lazy_static
is a crate which allows you to lazily initialize static items. For example:
lazy_static!
I was curious how it all worked under the hood. However I didn't spend enough time actually reading all the code. Instead, I got as far as the no_std
implementation which uses the spin
crate's Once
implementation.
lazy_static!
lazy-static relies on two things to work: A type to hold the data that you'd want to store in a static item; And a Deref
implementation on an arbitrary wrapper type. The latter is implemented per static item.
The idea is that the final static item is actually an instance of the arbitrary type which can then be dererenced to fetch the data held by a Lazy instance it knows about. So if you wanted to have a static item LIST: Vec<&'static str>
, you would implement the wrapper type like this:
;
static SET: StaticSet = StaticSet;
StaticList
is unique for every lazy static item and it is what you initialize the static item with since it is const
. When it is accessed (and automatically dereferenced), for example SET.get("One")
, the Deref
impl will run the lazy evaluation logic.
For the Lazy
type, we want to be able to pass in a closure which would initialize and return the static item's value and which should only ever be called once. Subsequent calls should always return the result of that closures evaluation.
use ;
const fn new()
so that we can initialize the LAZY
static item in the deref implementation. get
will call the init
closure exactly once, so we can guarantee that after the closure has returned, the data will be written in data
.
It turned out lazy_static has a no_std and a std implementation which I completely neglected to notice until it was too late. Having a look at the std implementation, they're using std::cell::Cell
instead of Mutex
which I was using. Cell is Send
, but not Sync
however, so they've implemented Sync
for the Lazy type. In this instance we wouldn't need to worry about Cell's interior mutability not being synchronized across threads because we write to it exactly once when the init closure returns.
use ;
unsafe
Lazy::new(|| {})
And then I noticed this error:
44 | static SET: HashSet<&'static str> = HashSet::new();
| ^^^^^^^^^^^^^^
|
= note: calls in statics are limited to constant functions, tuple structs and tuple variants
= note: consider wrapping this expression in `Lazy::new(|| ...)` from the `once_cell` crate: https://crates.io/crates/once_cell
The compiler recommends to use once_cell
for lazy static items...
I had a look at once_cell::Lazy
and it's a very similar data structure to what I had before.
The difference between once_cell's implementation and lazy_static is that with once_cell::Lazy, you explicitly use the Lazy
type, whereas lazy_static generates individual types for each static item:
use Lazy;
static SET: = new;
This required some modifications to the original code, but not too much. Instead of having an arbitrary wrapper type which implements Deref, I could implement Deref on the Lazy type. new
would take the init closure, and that closure would need to be stored as a field on the type. The only change in get
is to retrieve the init closure out of the struct, and make the function itself private:
unsafe