[−][src]Crate once_cell
Overview
once_cell
provides two new cell-like types, unsync::OnceCell
and sync::OnceCell
. OnceCell
might store arbitrary non-Copy
types, can be assigned to at most once and provide direct access
to the stored contents. In a nutshell, API looks roughly like this:
impl<T> OnceCell<T> { fn new() -> OnceCell<T> { ... } fn set(&self, value: T) -> Result<(), T> { ... } fn get(&self) -> Option<&T> { ... } }
Note that, like with RefCell
and Mutex
, the set
method requires only a shared reference.
Because of the single assignment restriction get
can return an &T
instead of Ref<T>
or MutexGuard<T>
.
The sync
flavor is thread-safe (that is, implements Sync
) trait, while the unsync
one is not.
Patterns
OnceCell
might be useful for a variety of patterns.
Safe Initialization of global data
use std::{env, io}; use once_cell::sync::OnceCell; #[derive(Debug)] pub struct Logger { // ... } static INSTANCE: OnceCell<Logger> = OnceCell::new(); impl Logger { pub fn global() -> &'static Logger { INSTANCE.get().expect("logger is not initialized") } fn from_cli(args: env::Args) -> Result<Logger, std::io::Error> { // ... } } fn main() { let logger = Logger::from_cli(env::args()).unwrap(); INSTANCE.set(logger).unwrap(); // use `Logger::global()` from now on }
Lazy initialized global data
This is essentially lazy_static!
macro, but without a macro.
use std::{sync::Mutex, collections::HashMap}; use once_cell::sync::OnceCell; fn global_data() -> &'static Mutex<HashMap<i32, String>> { static INSTANCE: OnceCell<Mutex<HashMap<i32, String>>> = OnceCell::new(); INSTANCE.get_or_init(|| { let mut m = HashMap::new(); m.insert(13, "Spica".to_string()); m.insert(74, "Hoyten".to_string()); Mutex::new(m) }) }
There are also sync::Lazy
and unsync::Lazy
convenience types to streamline this pattern:
use std::{sync::Mutex, collections::HashMap}; use once_cell::sync::Lazy; static GLOBAL_DATA: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| { let mut m = HashMap::new(); m.insert(13, "Spica".to_string()); m.insert(74, "Hoyten".to_string()); Mutex::new(m) }); fn main() { println!("{:?}", GLOBAL_DATA.lock().unwrap()); }
General purpose lazy evaluation
Unlike lazy_static!
, Lazy
works with local variables.
use once_cell::unsync::Lazy; fn main() { let ctx = vec![1, 2, 3]; let thunk = Lazy::new(|| { ctx.iter().sum::<i32>() }); assert_eq!(*thunk, 6); }
If you need a lazy field in a struct, you probably should use OnceCell
directly, because that will allow you to access self
during initialization.
use std::{fs, path::PathBuf}; use once_cell::unsync::OnceCell; struct Ctx { config_path: PathBuf, config: OnceCell<String>, } impl Ctx { pub fn get_config(&self) -> Result<&str, std::io::Error> { let cfg = self.config.get_or_try_init(|| { fs::read_to_string(&self.config_path) })?; Ok(cfg.as_str()) } }
Building block
Naturally, it is possible to build other abstractions on top of OnceCell
.
For example, this is a regex!
macro which takes a string literal and returns an
expression that evaluates to a &'static Regex
:
macro_rules! regex { ($re:literal $(,)?) => {{ static RE: once_cell::sync::OnceCell<regex::Regex> = once_cell::sync::OnceCell::new(); RE.get_or_init(|| regex::Regex::new($re).unwrap()) }}; }
This macro can be useful to avoid "compile regex on every loop iteration" problem.
Comparison with std
!Sync types | Access Mode | Drawbacks |
---|---|---|
Cell<T> | T | requires T: Copy for get |
RefCel<T> | RefMut<T> / Ref<T> | may panic at runtime |
unsync::OnceCell<T> | &T | assignable only once |
Sync types | Access Mode | Drawbacks |
---|---|---|
AtomicT | T | works only with certain Copy types |
Mutex<T> | MutexGuard<T> | may deadlock at runtime, may block the thread |
sync::OnceCell<T> | &T | assignable only once, may block the thread |
Technically, calling get_or_init
will also cause a panic or a deadlock if it recursively calls
itself. However, because the assignment can happen only once, such cases should be more rare than
equivalents with RefCell
and Mutex
.
Minimum Supported rustc
Version
This crate's minimum supported rustc
version is 1.31.1
.
If only std
feature is enabled, MSRV will be updated conservatively.
When using other features, like parking_lot
, MSRV might be updated more frequently, up to the latest stable.
In both cases, increasing MSRV is not considered a semver-breaking change.
Implementation details
Implementation is based on lazy_static
and lazy_cell
crates and std::sync::Once
. In some sense,
once_cell
just streamlines and unifies those APIs.
To implement a sync flavor of OnceCell
, this crates uses either a custom re-implementation of
std::sync::Once
or parking_lot::Mutex
. This is controlled by the parking_lot
feature, which
is enabled by default. Performance is the same for both cases, but parking_lot
based OnceCell<T>
is smaller by up to 16 bytes.
This crate uses unsafe.
F.A.Q.
Should I use lazy_static or once_cell?
To the first approximation, once_cell
is both more flexible and more convenient than lazy_static
and should be preferred.
Unlike once_cell
, lazy_static
supports spinlock-based implementation of blocking which works with
#![no_std]
.
lazy_static
has received significantly more real world testing, but once_cell
is also a widely
used crate.
Should I use sync or unsync flavor?
Because Rust compiler checks thread safety for you, it's impossible to accidentally use unsync
where
sync
is required. So, use unsync
in single-threaded code and sync
in multi-threaded. It's easy
to switch between the two if code becomes multi-threaded later.
At the moment, unsync
has an additional benefit that reentrant initialization causes a panic, which
might be easier to debug than a deadlock.
Related crates
Modules
sync | |
unsync |