#cell #refcell #interior-mutability #borrowing #send-sync

no-std token-ref-cell

Interior mutability cell using an external token to synchronize accesses

1 unstable release

0.1.1 Aug 31, 2024
0.1.0 Aug 8, 2024

#556 in Rust patterns

Download history 102/week @ 2024-08-05 7/week @ 2024-08-12 200/week @ 2024-08-26 12/week @ 2024-09-02 22/week @ 2024-09-16 8/week @ 2024-09-23 33/week @ 2024-09-30

63 downloads per month

MIT license

55KB
1K SLoC

token-ref-cell

This library provides TokenRefCell, an interior mutability cell which uses an external Token reference to synchronize its accesses.

Contrary to other standard cells like RefCell, TokenRefCell is Sync as long as its token is Send + Sync; it can thus be used in multithreaded programs.

Multiple token implementations are provided, the easiest to use being the smart-pointer-based ones: every Box<T> can indeed be used as a token (as long as T is not a ZST). The recommended token implementation is BoxToken, and it's the default value of the generic parameter of TokenRefCell.

The runtime cost is very lightweight: only one pointer comparison for TokenRefCell::borrow/TokenRefCell::borrow_mut when using BoxToken (and zero-cost when using singleton_token!).
Because one token can be used with multiple cells, it's possible for example to use a single rwlock wrapping a token to synchronize mutable access to multiple Arc data.

Examples

use std::sync::{Arc, RwLock};
use token_ref_cell::{TokenRefCell, BoxToken};

let mut token = RwLock::new(BoxToken::new());
// Initialize a vector of arcs
let token_ref = token.read().unwrap();
let arc_vec = vec![Arc::new(TokenRefCell::new(0, &*token_ref)); 42];
drop(token_ref);
// Use only one rwlock to write to all the arcs
let mut token_mut = token.write().unwrap();
for cell in &arc_vec {
    *cell.borrow_mut(&mut *token_mut) += 1;
}
drop(token_mut)

Why another crate?

Many crates based on the same principle exists: qcell, ghost-cell, token-cell, singleton-cell, etc.

When I started writing token-ref-cell, I only knew token-cell one, as it was written by a guy previously working in the same company as me. But I wanted to take a new approach, notably designing an API closer than standard RefCell one, hence the name TokenRefCell. In fact, my goal was simple: to make the graph example compile, and for that I needed to enable "re-borrowing", i.e. reusing the token used in a mutable borrow to mutably borrow a "sub-cell".

When I was satisfied with the result, before publishing it, I search for other similar crates, and I found the list above, and realized I'd reimplemented the same concept as a bunch of people, especially qcell which uses a Box for its cell token/owner. However, this fresh implementation still has a few specificities which makes it relevant:

  • a familiar API close to RefCell one;
  • a unified API with a single Token trait, contrary to qcell which provides four different cell types;
  • a larger choice of token implementations, thanks to the simplicity of the Token trait: singleton types, smart-pointers, pinned/unpinned references, etc.;
  • no_std implementation, compatible with custom allocators (doesn't require alloc crate, and doesn't require allocator at all using RefToken for example);
  • re-borrowing.

Dependencies