Skip to content

compio-rs/thin-cell

thin-cell

MIT licensed crates.io docs.rs Check Test Telegram

A compact smart pointer combining reference counting and interior mutability.

ThinCell is a space-efficient alternative to Rc<RefCell<T>> or Arc<Mutex<T>> that itself is always 1 pointer-sized no matter if T is Sized or not (like ThinBox), compare to Rc<RefCell<T>> which is 2 pointer-sized for T: !Sized.

Features

  • One-usize pointer, no matter what T is
  • Reference counted ownership (like Rc or Arc)
  • Interior mutability with only mutable borrows (so it only needs 1-bit to track borrow state)
  • Both sync and unsync versions, with the same API and slightly different behavior on borrow rules (see below)

How It Works

ThinCell achieves its compact representation by storing metadata inline at offset 0 of the allocation (for unsized types) like ThinBox does.

Simplified layout:

struct Inner<T> {
    metadata: usize,
    state: usize
    data: T,
}

Borrow Semantics

Unlike RefCell which supports multiple immutable borrows OR one mutable borrow, ThinCell only supports one mutable borrow at a time. Attempting to borrow while already borrowed will:

  • sync::ThinCell<T>: Block current thread by busy-looping and yield to other threads;
  • unsync::ThinCell<T>: Panic immediately.

try_borrow is available for both versions, which returns None instead of panicking or blocking when already borrowed.

Examples

Basic Usage

# use thin_cell::unsync::ThinCell;
let cell = ThinCell::new(42);

// Clone to create multiple owners
let cell2 = cell.clone();

// Borrow mutably
{
    let mut borrowed = cell.borrow();
    *borrowed = 100;
} // borrow is released here

// Access from another owner
assert_eq!(*cell2.borrow(), 100);

With Trait Objects (Unsized Types)

Due to limitation of stable rust, or in particular, the lack of CoerceUnsized, creating a ThinCell<dyn Trait> from a concrete type requires manual coercion, and that coercion's safety has to be guaranteed by the user. Normally just ptr as *const Inner<MyUnsizedType> or ptr as _ with external type annotation is good enough:

# use thin_cell::unsync::ThinCell;
trait Animal {
    fn speak(&self) -> &str;
}

struct Dog;

impl Animal for Dog {
    fn speak(&self) -> &str {
        "Woof!"
    }
}

// Create a ThinCell<dyn Animal> from a concrete type
// Or you can write `unsafe { ThinCell::new_unsize(Dog, |p| p as *const Inner<dyn Animal>) };`
let cell: ThinCell<dyn Animal> = unsafe { ThinCell::new_unsize(Dog, |p| p as _) };

// Still only 1 word of storage!
assert_eq!(std::mem::size_of_val(&cell), std::mem::size_of::<usize>());

Borrow Checking

use thin_cell::unsync::ThinCell;

let cell = ThinCell::new(42);

let borrow1 = cell.borrow();
let borrow2 = cell.borrow(); // Panics! Already borrowed

Use try_borrow for non-panicking behavior:

# use thin_cell::unsync::ThinCell;
let cell = ThinCell::new(42);

let borrow1 = cell.borrow();
assert!(cell.try_borrow().is_none()); // Returns None instead of panicking

About

A compact, single-threaded smart pointer combining reference counting and interior mutability

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors