An alternative to `qcell` and `ghost-cell` that instead uses const generics

Overview

Purpose

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 usizes.

Pros

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.

Cons

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, Tokens must be built using a TokenBuilder struct that ensures IDs 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().

Example

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));

Future improvements

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).

Should I use this?

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.

Comments
  • Bounds on `Sync for Cell<T, ID>` are insufficient

    Bounds on `Sync for Cell` are insufficient

    Since Cell::borrow_mut() allows a &Cell<T, ID> to be converted into a &mut T (i.e., Cell<T, ID> is an interior mutability type), Cell<T, ID> should be Sync only if T: Send + Sync. However, the current implementation only requires T: Sync. To illustrate the issue:

    // frankencell = "=0.1.0"
    // cargo +nightly miri run
    
    #![feature(exclusive_wrapper)]
    
    use frankencell::Cell;
    use std::{cell::Cell as StdCell, sync::Exclusive, thread};
    
    fn main() {
        let mut token = frankencell::first().unwrap().token().0;
        let s = StdCell::new("Hello, world!");
        let cell = Cell::new(Exclusive::new(&s));
        thread::scope(|scope| {
            scope.spawn(|| {
                cell.borrow_mut(&mut token).get_mut().set("Hello, Rust!");
            });
            println!("{}", s.get());
        });
    }
    
    error: Undefined Behavior: Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>` at alloc1637. (2) just happened here
       --> /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cell.rs:413:31
        |
    413 |         mem::replace(unsafe { &mut *self.value.get() }, val)
        |                               ^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) Read on thread `main` and (2) Write on thread `<unnamed>` at alloc1637. (2) just happened here
        |
    help: and (1) occurred earlier here
       --> src/main.rs:17:24
        |
    17  |         println!("{}", s.get());
        |                        ^^^^^^^
        = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
        = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
        = note: BACKTRACE (of the first span):
        = note: inside `std::cell::Cell::<&str>::replace` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cell.rs:413:31: 413:53
        = note: inside `std::cell::Cell::<&str>::set` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cell.rs:363:19: 363:36
    note: inside closure
       --> src/main.rs:15:13
        |
    15  |             cell.borrow_mut(&mut token).get_mut().set("Hello, Rust!");
        |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    error: aborting due to previous error
    
    opened by LegionMammal978 1
  • v0.2.0

    v0.2.0

    • Fixed Cargo.toml to publish
    • Fixed warnings
    • Fixed repository thing
    • Add non-pub PhantomData to TokenBuilder and TokenWith
    • Add Send requirement on impl Sync for Cell
    • Fixed Debug impl, removed Display impl
    • Make the first() function thread-safe
    • Added init_tokens, fixed tests
    • Updated README
    • Update to v0.2.0
    opened by spencerwhite 0
  • TokenBuilder can be constructed

    TokenBuilder can be constructed

    builder::TokenBuilder has no private fields and can be constructed by anyone outside the API. Will add a PhantomData marker to prevent this. Also do this with TokenWith to prevent having to use getters and setters.

    opened by spencerwhite 0
  • `Debug` and `Display` implementations unsoundly call `Cell::get()`

    `Debug` and `Display` implementations unsoundly call `Cell::get()`

    This allows an immutable reference to alias with a mutable reference returned from Cell::borrow_mut().

    // frankencell = "=0.1.0"
    // cargo +nightly miri run
    
    use frankencell::Cell;
    
    fn main() {
        let mut token = frankencell::first().unwrap().token().0;
        let cell = Cell::new("Hello, world!");
        let s = cell.borrow_mut(&mut token);
        println!("{cell}");
        println!("{s}");
    }
    
    error: Undefined Behavior: trying to retag from <3165> for SharedReadOnly permission at alloc1645[0x0], but that tag does not exist in the borrow stack for this location
        --> /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:2406:1
         |
    2406 | fmt_refs! { Debug, Display, Octal, Binary, LowerHex, UpperHex, LowerExp, UpperExp }
         | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         | |
         | trying to retag from <3165> for SharedReadOnly permission at alloc1645[0x0], but that tag does not exist in the borrow stack for this location
         | this error occurs as part of retag at alloc1645[0x0..0x10]
         |
         = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
         = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
    help: <3165> was created by a Unique retag at offsets [0x0..0x10]
        --> src/main.rs:9:13
         |
    9    |     let s = cell.borrow_mut(&mut token);
         |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    help: <3165> was later invalidated at offsets [0x0..0x10] by a SharedReadOnly retag
        --> src/main.rs:10:5
         |
    10   |     println!("{cell}");
         |     ^^^^^^^^^^^^^^^^^^
         = note: BACKTRACE (of the first span):
         = note: inside `<&mut &str as std::fmt::Display>::fmt` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:2400:71: 2400:78
         = note: inside `std::fmt::write` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:1232:17: 1232:59
         = note: inside `<std::io::StdoutLock<'_> as std::io::Write>::write_fmt` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/mod.rs:1682:15: 1682:43
         = note: inside `<&std::io::Stdout as std::io::Write>::write_fmt` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:715:9: 715:36
         = note: inside `<std::io::Stdout as std::io::Write>::write_fmt` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:689:9: 689:33
         = note: inside `std::io::stdio::print_to::<std::io::Stdout>` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:1007:21: 1007:47
         = note: inside `std::io::_print` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:1074:5: 1074:37
    note: inside `main`
        --> src/main.rs:11:5
         |
    11   |     println!("{s}");
         |     ^^^^^^^^^^^^^^^
         = note: this error originates in the macro `fmt_refs` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    error: aborting due to previous error
    
    opened by LegionMammal978 0
  • `frankencell::first()` results in a data race when called from multiple threads

    `frankencell::first()` results in a data race when called from multiple threads

    To illustrate, this program raises an error under Miri:

    // frankencell = "=0.1.0"
    // cargo +nightly miri run
    
    use std::thread;
    
    fn main() {
        thread::spawn(|| {
            frankencell::first();
        });
        frankencell::first();
    }
    
    error: Undefined Behavior: Data race detected between (1) Write on thread `main` and (2) Write on thread `<unnamed>` at alloc2958. (2) just happened here
        --> /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:1621:23
         |
    1621 |     pub const fn take(&mut self) -> Option<T> {
         |                       ^^^^^^^^^ Data race detected between (1) Write on thread `main` and (2) Write on thread `<unnamed>` at alloc2958. (2) just happened here
         |
    help: and (1) occurred earlier here
        --> src/main.rs:10:5
         |
    10   |     frankencell::first();
         |     ^^^^^^^^^^^^^^^^^^^^
         = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
         = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
         = note: BACKTRACE (of the first span):
         = note: inside `std::option::Option::<frankencell::TokenBuilder<0>>::take` at /home/lm978/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:1621:23: 1621:32
         = note: inside `frankencell::first` at /home/lm978/.cargo/registry/src/github.com-1ecc6299db9ec823/frankencell-0.1.0/src/lib.rs:93:14: 93:26
    note: inside closure
        --> src/main.rs:8:9
         |
    8    |         frankencell::first();
         |         ^^^^^^^^^^^^^^^^^^^^
    
    note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
    
    error: aborting due to previous error
    

    For this pattern, I'd suggest using a Once:

    use std::sync::Once;
    
    static FIRST: Once = Once::new();
    
    pub fn first() -> Option<TokenBuilder<0>> {
        let mut builder = None;
        FIRST.call_once(|| {
            // SAFETY: `Once` guarantees that this will only be called at most once.
            builder = Some(unsafe { TokenBuilder::new() });
        });
        builder
    }
    
    opened by LegionMammal978 0
Owner
SpencerBeige
SpencerBeige
Like a cell, but make lifetimes dynamic instead of ownership

LendingCell is a mutable container that allows you to get an owned reference to the same object. When the owned reference is dropped, ownership return

Kalle Samuels 19 Dec 15, 2022
A discord bot for detecting ghost pings

Anti Ghost Ping Status This is not the production bot code nor a working bot, just a rewrite in rust. How to Run Requirements: Postgres db Fill out .e

Anti Ghost Ping 3 Aug 9, 2022
Rust Offensive Security Library for making you .EXE go GHOST 🥷🏾

Ghost Ghost is a rust library that allows you to delete your executable while it's running. Usage // With a default placeholder value on windows (`svc

Mohammed Maali 7 Apr 17, 2023
Statically allocated, runtime initialized cell.

static-cell Statically allocated, initialized at runtime cell. StaticCell provides a no-std-compatible, no-alloc way to reserve memory at compile time

null 4 Oct 2, 2022
Extension trait to chunk iterators into const-length arrays.

const-chunks This crate provides an extension trait that lets you chunk iterators into constant-length arrays using const generics. See the docs for m

Louis Gariépy 6 Jun 12, 2023
Asserts const generic expressions at build-time.

build_assert build_assert allows you to make assertions at build-time. Unlike assert and some implementations of compile-time assertions, such as stat

MaxXing 4 Nov 23, 2023
Thread-safe cell based on atomic pointers to externally stored data

Simple thread-safe cell PtrCell is an atomic cell type that allows safe, concurrent access to shared data. No std, no data races, no nasal demons (UB)

Nikolay Levkovsky 3 Mar 23, 2024
A tool that allow you to run SQL-like query on local files instead of database files using the GitQL SDK.

FileQL - File Query Language FileQL is a tool that allow you to run SQL-like query on local files instead of database files using the GitQL SDK. Sampl

Amr Hesham 39 Mar 12, 2024
A general-purpose, transactional, relational database that uses Datalog and focuses on graph data and algorithms

cozo A general-purpose, transactional, relational database that uses Datalog for query and focuses on graph data and algorithms. Features Relational d

null 1.9k Jan 9, 2023
Uses the cardano mini-protocols to receive every block and transaction, and save them to a configurable destination

cardano-slurp Connects to one or more cardano-node's, streams all available transactions, and saves them to disk (or to S3) in raw cbor format. Usage

Pi Lanningham 16 Jan 31, 2023
Conference Monitoring Project based on Image Recognition that uses Rust Language and AWS Rekognition service to get the level of image similarity.

Conference Monitoring System based on Image Recognition in Rust This is a Conference Monitoring Project based on Image Recognition that uses Rust Lang

Pankaj Chaudhary 6 Dec 18, 2022
This is choose, a human-friendly and fast alternative to cut and (sometimes) awk

Choose This is choose, a human-friendly and fast alternative to cut and (sometimes) awk Features terse field selection syntax similar to Python's list

Ryan Geary 1.4k Jan 7, 2023
A more compact and intuitive ASCII table in your terminal: an alternative to "man 7 ascii" and "ascii"

asciit A more compact and intuitive ASCII table in your terminal: an alternative to man 7 ascii and ascii. Colored numbers and letters are much more e

Qichen Liu 刘启辰 5 Nov 16, 2023
Like grep, but uses tree-sitter grammars to search

tree-grepper Works like grep, but uses tree-sitter to search for structure instead of strings. Installing This isn't available packaged anywhere. That

Brian Hicks 219 Dec 25, 2022
📺(tv) Tidy Viewer is a cross-platform CLI csv pretty printer that uses column styling to maximize viewer enjoyment.

??(tv) Tidy Viewer is a cross-platform CLI csv pretty printer that uses column styling to maximize viewer enjoyment.

Alex Hallam 1.8k Jan 2, 2023
hexyl is a simple hex viewer for the terminal. It uses a colored output to distinguish different categories of bytes

hexyl is a simple hex viewer for the terminal. It uses a colored output to distinguish different categories of bytes (NULL bytes, printable ASCII characters, ASCII whitespace characters, other ASCII characters and non-ASCII).

David Peter 7.3k Dec 29, 2022
A terminal clock that uses 7-segment display characters

Seven-segment clock (7clock) 7clock.3.mp4 This is a clock for terminals that uses the Unicode seven-segment display characters added in Unicode 13.0.

Wesley Moore 4 Nov 11, 2022
ChatGPT-Code-Review is a Rust application that uses the OpenAI GPT-3.5 language model to review code

ChatGPT-Code-Review is a Rust application that uses the OpenAI GPT-3.5 language model to review code. It accepts a local path to a folder containing code, and generates a review for each file in the folder and its subdirectories.

Greg P. 15 Apr 22, 2023
Library uses file-based mmap to store key-values

Library uses file-based mmap to store key-values This is a Rust version of MMKV. By default, this lib uses CRC8 to check data integrity. If include fe

Kexuan Yang 4 Aug 14, 2023