Crates.io | frankencell |
lib.rs | frankencell |
version | 0.2.0 |
source | src |
created_at | 2023-02-05 23:41:05.248242 |
updated_at | 2023-02-07 21:17:46.959541 |
description | An alternative to `qcell` and `ghost-cell` that instead uses const generics |
homepage | |
repository | https://github.com/spencerwhite/frankencell |
max_upload_size | |
id | 777517 |
size | 20,811 |
This crate is another attempt at the ghost-cell
/ qcell
saga of cell crates. This provides
an alternative to std::cell::RefCell
that can allow interior mutability checked at compile
time, rather than runtime. Because Rust doesn't allow for unlimited creation of invariant
generics, this always comes with a rather large complexity cost. Whereas ghost-cell
uses
invariant lifetimes and qcell
can use either invariant lifetimes or newtypes, this crate
instead uses const generic usize
s.
As with other *cell
crates, this model provides interior mutability checked at compile time.
Unlike ghost-cell
's model, this crate doesn't require all of your borrows to exist in a
closure and, unlike qcell::TCell
, this crate allows for more than three simultaneous borrows.
First, any item that contains a Cell
or Token
must be generic over const ID: usize
. You
may choose to get rid of this if you are sure that, for example, a certain instance of a
struct will always have ID = 3
Second, in order to provide a safe API, Token
s must be built using a TokenBuilder
struct
that ensures ID
s are unique. There may in the future be a way around this, but don't hold
your breath!
Third, the use of explicit usize
discriminants makes passing a Cell
or Token
to outside
crates inherently unsafe. It is recommended to instead send raw values, e.g., with
Cell::into_inner()
.
use frankencell::*;
let (token1, next) = first().unwrap().token();
let (token2, _) = next.token();
let a = Cell::new('a');
let b = Cell::new('b');
println!("{}", a.borrow(&token1));
println!("{}", b.borrow(&token2));
// The following fails to compile:
println!("{}", a.borrow(&token2));
println!("{}", b.borrow(&token1));
Currently because of how const
works, it is impossible for a const fn
to return different
values on different calls. In order to generate unique IDs however, the following would have to
be possible:
const fn inc() -> usize {
// Insert magic here
}
#[test]
fn test_inc() {
assert_eq!(inc(), 0);
assert_eq!(inc(), 1);
assert_eq!(inc(), 2);
// user-facing API is now significantly better
let token_3: Token<3> = Token::next();
let token_4: Token<4> = Token::next();
}
This may become possible when/if heap allocations are allowed in const
contexts, but even
then this pattern will likely never be officially endorsed by the Rust compiler.
It may also be possible with macros when/if macros are allowed to keep a local state (rust-lang/rust issue 44034).
Probably not. At the moment this is really more of a proof-of-concept. There's still a lot of work that needs to go into the compiler and, even then, this may not be a viable solution.
If you're simply looking for something that's more ergonomic than ghost-cell
and qcell
, the
cell-family
crate seems to have a good approach.
cell-family` crate seems to have a good approach.