Skip to content
Rust library for single assignment cells and lazy statics without macros
Branch: master
Clone or download
bors and matklad Merge #25
25: Specify MSRV policy r=matklad a=matklad

cc @BurntSushi

I think I can do a neat trick of guaranteeing conservative MSRV if features are disabled. 

With `parking_lot` feature enabled, I am bound to at least that crate's MSRV, so I can't make promises.

Additionally, at some point, when `std::sync::Mutex::new` becomes `const`, I'd like to take advantage of that. I plan to add a *default* `std_once_cell_get_or_try_init` feature at that moment, which will bump MSRV *if* you use default features. 


Also, the crate's version is currently 0.2, but, API-wise, I am ready to do 1.0. However I'd prefer to do that later (months?) lest someone finds a critical issue with the current interface.

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
Latest commit d5054ca Jun 7, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
design Add files via upload Aug 8, 2018
examples print size in the benchmark Jun 7, 2019
src specify MSRV policy Jun 7, 2019
tests implement [Ref]UnwindSafe for OnceCell Jun 5, 2019
.gitignore Initial Aug 2, 2018
.travis.yml fix CI for bors Jun 5, 2019
CHANGELOG.md release v0.2.1 Jun 5, 2019
Cargo.lock.min v0.2.0 May 7, 2019
Cargo.toml release v0.2.1 Jun 5, 2019
LICENSE-APACHE Initial Aug 2, 2018
LICENSE-MIT Initial Aug 2, 2018
README.md specify MSRV policy Jun 7, 2019
logo.svg Add files via upload Aug 8, 2018
rustfmt.toml reformat Jun 5, 2019

README.md

once_cell

Build Status Crates.io API reference

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

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> {
       // ...
#      Ok(Logger {})
    }
}

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

Comparison with std

!Sync types Access Mode Drawbacks
Cell<T> T works only with Copy types
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 optional features are not enabled (default-features = false in Cargo.toml), MSRV will be updated conservatively. When using specific features or default features, 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 in some sense just streamlines and unifies the APIs of those crates.

To implement a sync flavor of OnceCell, this crates uses either std::sync::Once or parking_lot::Mutex. This is controlled by the parking_lot feature, which is enabled by default.

This crate uses unsafe.

Related crates

You can’t perform that action at this time.