#tags #static #thread-safe #lazy-evaluation #macro #zst

tagged_cell

Fast, initializable, and thread safe static variables

4 releases

0.1.3 Mar 16, 2022
0.1.2 Mar 13, 2022
0.1.1 Sep 12, 2021
0.1.0 Sep 12, 2021

#2420 in Rust patterns

MIT/Apache

8KB
68 lines

tagged_cell

CI Crates.io API reference

Fast, initializable, and thread safe static variables

Borrows the excellent ZST based tagging implementation to guarantee cells are initialized exactly once before an access is attempted.

This is implemented via the [TaggedCell] and a Tag type, which must be unique for each instance of the [TaggedCell] for safe operation. The [TaggedCell] must then be set up via init(), which initializes the underlying data using a user provided function or closure, and then returns a special zero-sized [Init] tag used to access the Cell's data.

use tagged_cell::TaggedCell;
struct FooTag;
static FOO: TaggedCell<usize, FooTag> = unsafe {TaggedCell::new()};

// Initialize the cell's data and retrieve a tag
let tag = FOO.init(|| 27);

// The tag is required to obtain a shared reference to the data
assert_eq!(*FOO.get(tag), 27);

To ensure unique tag types are used for each cell, and to 'wrap' the unsafe call, the [tagged_cell!] macro is provided. The macro creates a new tag type based on the variable's name, and applies it in the declaration.

use tagged_cell::tagged_cell;
tagged_cell!{
   static BAR: TaggedCell<Vec<usize>, _> = TaggedCell::new();
}

let tag = BAR.init(|| vec![0, 10, 20]);
let vec = BAR.get(tag);

assert_eq!(vec[2], 20);

When unique tag types are used, attempting to access a [TaggedCell] before it is initialized will cause a compilation error.

use tagged_cell::tagged_cell;

tagged_cell!{
    static BAZ: TaggedCell<usize>, _> = TaggedCell::new();
}
tagged_cell!{
    static QUX: TaggedCell<usize>, _> = TaggedCell::new();
}

// read before init is not possible
BAZ.get(Init{BAZ::TagType});

let qux_tag = QUX.init(|| 35);

// using the wrong tag throws an error
BAZ.get(qux_tag);

To allow for usage across threads, only the first invocation of init() will initialize the Cell's data. All future init() calls will just return a new tag. It is undetermined which thread will initialize the Cell's data.

use std::thread;
use tagged_cell::tagged_cell;

tagged_cell!{
    static TABLE: TaggedCell<Vec<usize>, _> = TaggedCell::new();
}

thread::spawn(move || {
    let tag = TABLE.init(|| vec![0, 10, 20]);
    let table = TABLE.get(tag);
    assert_eq!(table[2], 20);
});

thread::spawn(move || {
    let tag = TABLE.init(|| vec![0, 10, 20]);
    let table = TABLE.get(tag);
    assert_eq!(table[1], 10);
});

No runtime deps