[][src]Crate once_cell


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:

This example is not tested
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>.


OnceCell might be useful for a variety of patterns.

Safe Initialization of global data

use std::{env, io};

use once_cell::sync::OnceCell;

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

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

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(|| {
    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(|| {

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 typesAccess ModeDrawbacks
Cell<T>Trequires T: Copy for get
RefCel<T>RefMut<T> / Ref<T>may panic at runtime
unsync::OnceCell<T>&Tassignable only once
Sync typesAccess ModeDrawbacks
AtomicTTworks only with certain Copy types
Mutex<T>MutexGuard<T>may deadlock at runtime, may block the thread
sync::OnceCell<T>&Tassignable 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.

Related crates