Crate cryo[−][src]
Requires Rust 1.34.0 or later.
This crate provides a cell-like type Cryo
that is similar to RefCell
except that it constrains the lifetime of its borrowed value
through a runtime check mechanism, erasing the compile-time lifetime
information. The lock guard CryoRef
created from Cryo
is
'static
and therefore can be used in various situations that require
'static
types, including:
- Storing
CryoRef
temporarily in astd::any::Any
-compatible container. - Capturing a reference to create a Objective-C block.
This works by, when a Cryo
is dropped, not letting the current thread’s
execution move forward (at least¹) until all references to the expiring
Cryo
are dropped so that none of them can outlive the Cryo
.
This is implemented by readers-writer locks under the hood.
¹ SyncLock
blocks the current thread’s execution on lock failure.
LocalLock
, on the other hand, panics because it’s designed for
single-thread use cases and would deadlock otherwise.
Examples
cryo!
, Cryo
, and LocalLock
(single-thread lock
implementation, used by default):
use std::{thread::spawn, pin::Pin}; let cell: usize = 42; { // `cryo!` uses `LocalLock` by default cryo!(let cryo: Cryo<usize> = &cell); // Borrow `cryo` and move it into a `'static` closure. let borrow: CryoRef<usize, _> = cryo.borrow(); let closure: Box<dyn Fn()> = Box::new(move || { assert_eq!(*borrow, 42); }); closure(); drop(closure); // Compile-time lifetime works as well. assert_eq!(*cryo.get(), 42); // When `cryo` is dropped, it will block until there are no other // references to `cryo`. In this case, the program will leave // this block immediately because `CryoRef` has already been dropped. }
cryo!
, Cryo
, and SyncLock
(thread-safe lock implementation):
use std::{thread::spawn, pin::Pin}; let cell: usize = 42; { // This this we are specifying the lock implementation cryo!(let cryo: Cryo<usize, SyncLock> = &cell); // Borrow `cryo` and move it into a `'static` closure. // `CryoRef` can be sent to another thread because // `SyncLock` is thread-safe. let borrow: CryoRef<usize, _> = cryo.borrow(); spawn(move || { assert_eq!(*borrow, 42); }); // Compile-time lifetime works as well. assert_eq!(*cryo.get(), 42); // When `cryo` is dropped, it will block until there are no other // references to `cryo`. In this case, the program will not leave // this block until the thread we just spawned completes execution. }
{ cryo!(let cryo_mut: CryoMut<usize, SyncLock> = &mut cell); // Borrow `cryo_mut` and move it into a `'static` closure. let mut borrow: CryoMutWriteGuard<usize, _> = cryo_mut.write(); spawn(move || { *borrow = 1; }); // When `cryo_mut` is dropped, it will block until there are no other // references to `cryo_mut`. In this case, the program will not leave // this block until the thread we just spawned completes execution } assert_eq!(cell, 1);
Don’t do these:
// The following statement will DEADLOCK because it attempts to drop // `Cryo` while a `CryoRef` is still referencing it, and `Cryo`'s // destructor will wait for the `CryoRef` to be dropped first (which // will never happen) let borrow = { cryo!(let cryo: Cryo<_, SyncLock> = &cell); cryo.borrow() };
// The following statement will ABORT because it attempts to drop // `Cryo` while a `CryoRef` is still referencing it, and `Cryo`'s // destructor will panic, knowing no amount of waiting would cause // the `CryoRef` to be dropped let borrow = { cryo!(let cryo: Cryo<_> = &cell); cryo.borrow() };
Caveats
- While it’s capable of extending the effective lifetime of a reference,
it does not apply to nested references. For example, when
&'a NonStaticType<'b>
is supplied toCryo
’s constructor, the borrowed type isCryoRef<NonStaticType<'b>>
, which is still partially bound to the original lifetime.
Details
Feature flags
-
std
(enabled by default) enablesSyncLock
. -
lock_api
enables the blanket implementation ofLock
on all types implementinglock_api::RawRwLock
, such asspin::RawRwLock
andparking_lot::RawRwLock
. -
atomic
(enabled by default) enables features that require full atomics, which is not supported by some targets (detecting such targets is still unstable (#32976)). This feature will be deprecated after the stabilization of #32976.
Overhead
Cryo<T, SyncLock>
’s creation, destruction, borrowing, and unborrowing
each take one or two atomic operations in the best cases.
Neither of SyncLock
and LocalLock
require dynamic memory allocation.
Nomenclature
From cryopreservation.
Macros
cryo | Construct a |
Structs
AtomicLock | atomic An implementation of |
Cryo | A cell-like type that enforces the lifetime restriction of its borrowed value at runtime. |
CryoMut | A cell-like type that enforces the lifetime restriction of its borrowed value at runtime. |
CryoMutReadGuard | The read lock guard type of |
CryoMutWriteGuard | The write lock guard type of |
LocalLock | A single-thread implementation of |
NoSendMarker | |
SendMarker | |
SyncLock | std An implementation of |
Traits
Lock | A trait for readers-writer locks. |
Type Definitions
CryoRef | The lock guard type of |