Cryo — Extend the lifetime of a reference. Safely.

docs.rs

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 a `std::any::Any`-compatible container. - Capturing a reference to create a [Objective-C block](https://crates.io/crates/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. [readers-writer locks]: https://en.wikipedia.org/wiki/Readers–writer_lock ¹ `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 `with_cryo`, `Cryo`, and `LocalLock` (single-thread lock implementation, used by default): ```rust use std::{thread::spawn, pin::Pin}; let cell: usize = 42; // `with_cryo` uses `LocalLock` by default with_cryo(&cell, |cryo: Pin<&Cryo<'_, usize, _>>| { // Borrow `cryo` and move it into a `'static` closure. let borrow: CryoRef = cryo.borrow(); let closure: Box = 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. }); ``` `with_cryo`, `Cryo`, and `SyncLock` (thread-safe lock implementation): ```rust use std::{thread::spawn, pin::Pin}; let cell: usize = 42; // This time we are specifying the lock implementation with_cryo((&cell, lock_ty::()), |cryo| { // Borrow `cryo` and move it into a `'static` closure. // `CryoRef` can be sent to another thread because // `SyncLock` is thread-safe. let borrow: CryoRef = 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. }); ``` `with_cryo`, `CryoMut`, and `SyncLock`: ```rust with_cryo((&mut cell, lock_ty::()), |cryo_mut| { // Borrow `cryo_mut` and move it into a `'static` closure. let mut borrow: CryoMutWriteGuard = 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: ```rust // 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 = with_cryo((&cell, lock_ty::()), |cryo| cryo.borrow()); ``` ```rust // 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 = with_cryo(&cell, |cryo| 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 to `Cryo`'s constructor, the borrowed type is `CryoRef>`, which is still partially bound to the original lifetime. ## Details ### Feature flags - `std` (enabled by default) enables `SyncLock`. - `lock_api` enables the blanket implementation of `Lock` on all types implementing `lock_api::RawRwLock`, such as [`spin::RawRwLock`][] and [`parking_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. [`spin::RawRwLock`]: https://docs.rs/spin/0.9.0/spin/type.RwLock.html [`parking_lot::RawRwLock`]: https://docs.rs/parking_lot/0.11.1/parking_lot/struct.RawRwLock.html [#32976]: https://github.com/rust-lang/rust/issues/32976 ### Overhead `Cryo`'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](https://en.wikipedia.org/wiki/Cryopreservation). License: MIT/Apache-2.0