#family #cell #define #owner #thread #unique #send

no-std cell-family

Cheap cells accessed through unique owners

1 unstable release

0.1.0 Dec 8, 2022

#737 in Concurrency

ISC license

53KB
718 lines

cell-family

Cells inspired by qcell::TCell / qcell::TLCell, with additional features.

Overview

cell-family provides the define! macro, which defines a new Family. For each family, a corresponding Cell and CellOwner can be created. Only a single CellOwner per family can exist at once, but multiple cells can exist at the same time.

For instance, you may define a family FooFamily as below:

cell_family::define!(type FooFamily: FooCellOwner for FooCell<T>);

This defines FooFamily (which implements Family) as well as FooCellOwner and FooCell, aliases for CellOwner<FooFamily> and Cell<FooFamily> respectively.

One FooCellOwner can exist per thread, and thus FooCellOwner is not Send, since sending a FooCellOwner to another thread may allow two FooCellOwners to co-exist in a single thread. To allow a single FooCellOwner per program (and thus make FooCellOwner Send), prefix define! with static:

cell_family::define!(static type FooFamily: FooCellOwner for FooCell<T>);

For both thread-local and thread-safe families, the API is the same:

let mut owner = FooCellOwner::new();
let a = FooCell::new(1);
let b = FooCell::new("bar");

assert_eq!(*a.get(&owner), 1);
assert_eq!(*b.get(&owner), "bar");

*a.get_mut(&mut owner) += 1;
*b.get_mut(&mut owner) = "baz";

assert_eq!(*a.get(&owner), 2);
assert_eq!(*b.get(&owner), "baz");
  • FooCell::new(T) simple wraps T in a #[repr(transparent)] FooCell without performing any checks.
  • FooCell::get(&FooCellOwner) and FooCell::get_mut(&mut FooCellOwner) are constant-time operations that return &T and &mut T respectively without performing any runtime checks. Since a single FooCellOwner exists per program (or thread), the aliasing rules of each cell is enforced by Rust through the FooCellOwner, which is borrowed as long as each FooCell is borrowed.
  • FooFamily ensures that a single FooCellOwner exists within a program; if another FooCellOwner exists, FooCellOwner::new() will panic. A try_new() counterpart exists to avoid crashing in such a case.

Benefits over qcell::TCell / qcell::TLCell

  • Unlike qcell::TCell (respectively qcell::TCell), the Family F is in charge of ensuring that a single CellOwner<F> exists per program (respectively thread). By using macros to generate families, we only need a single AtomicBool (respectively Cell<bool>) for each family, thus requiring no allocations.

  • A few additional methods are provided; for instance, owner.get(c), owner.get_mut(c) and owner.try_get_mut(c) are provided, where c can be:

    • A tuple of Cells.
    • An array of Cells.
    • An array of references to Cells.
    • A slice of Cells.
  • Thread-local and thread-safe Cells (and CellOwners) are backed by the same type; whether they are thread-local or thread-safe is determined by their Family: if it is thread-safe, it will also implement ThreadSafeFamily. This makes it easier to define generic functions over Cells.

  • cell-family fully supports #[no_std], except for thread-local families in non-nightly builds (since thread-local variables cannot be defined in #[no_std] without #[thread_local], which is not stable).

  • Cell is Debug, and will print a representation of its inner value if no CellOwner currently exists.

Dependencies