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:

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):

```rust use std::{thread::spawn, pin::Pin};

let cell: usize = 42;

{ // cryo! uses LocalLock by default cryo!(let cryo: Cryo = &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):

```rust use std::{thread::spawn, pin::Pin};

let cell: usize = 42;

{ // This this we are specifying the lock implementation cryo!(let cryo: Cryo = &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!, CryoMut, and SyncLock:

```rust { cryo!(let cryo_mut: CryoMut = &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:

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 = { cryo!(let cryo: Cryo<_, SyncLock> = &cell); 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 = { cryo!(let cryo: Cryo<_> = &cell); cryo.borrow() };

Caveats

Details

Feature flags

spin::RawRwLock: https://docs.rs/spin/0.9.0/spin/type.RwLock.html parking_lot::RawRwLock: https://docs.rs/parkinglot/0.11.1/parkinglot/struct.RawRwLock.html

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.

License: MIT/Apache-2.0