Expand description
§thin-cell
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-
usizepointer, no matter whatTis - Reference counted ownership (like
RcorArc) - Interior mutability with only mutable borrows (so it only needs 1-bit to track borrow state)
- Both
syncandunsyncversions, 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.
§Weak References (feature weak)
Enable the optional weak feature to get non-owning Weak<T> handles. A weak handle does not keep the value alive; the value is dropped when the last strong ThinCell is dropped, and the allocation is freed once the last Weak is dropped. Internally, the strong side holds one implicit weak reference like Rc/Arc. Use ThinCell::downgrade to create a Weak<T> and Weak::upgrade to try to regain a strong handle. Both sync and unsync versions expose strong_count and weak_count when the feature is on.
use thin_cell::sync::ThinCell;
let cell = ThinCell::new(String::from("hello"));
let weak = cell.downgrade();
assert_eq!(cell.strong_count(), 1);
assert_eq!(cell.weak_count(), 2);
let strong = weak.upgrade().unwrap();
assert_eq!(strong.strong_count(), 2);
drop(cell);
drop(strong);
assert!(weak.upgrade().is_none()); // value already dropped§Examples
§Basic Usage
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:
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 borrowedUse try_borrow for non-panicking behavior:
let cell = ThinCell::new(42);
let borrow1 = cell.borrow();
assert!(cell.try_borrow().is_none()); // Returns None instead of panicking