thin-cell 0.1.2

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

thin-cell

MIT licensed crates.io docs.rs Check Test Telegram

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

ThinCell is a space-efficient alternative to Rc and borrow_mut-only RefCell 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)
  • Interior mutability with only mutable borrows (so it only needs 1-bit to track borrow state)

How It Works

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

Overall layout:

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

Borrow Rules

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 panic with borrow or return None with try_borrow.

Examples

Basic Usage

# use thin_cell::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::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::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::ThinCell;
let cell = ThinCell::new(42);

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