once_cell

repository·master·Indexed 24 days ago

https://github.com/matklad/once_cell

A library providing single assignment cells (OnceCell) and lazy values (Lazy) for one-time initialization of values. It supports both single-threaded (unsync) and thread-safe (sync) implementations, allowing for direct reference access to stored contents without requiring RAII guards. It serves as a macro-free alternative to lazy_static! for defining global or local variables that are initialized only upon first access.

Tokens
3.1K
Snippets
10
Records
11
Agent score
35%

What's inside once_cell

  1. Overview of once_cell types

    master

    once_cell provides cell-like types that can be assigned at most once and provide direct access to the stored contents. It supports arbitrary non-Copy types.

    There are two main modules:

    • unsync::OnceCell: For single-threaded use.
    • sync::OnceCell: For thread-safe, synchronized use.

    Because of the single assignment restriction, the get method can return a direct reference &T instead of requiring a guard like Ref<T> or MutexGuard<T>.

    impl OnceCell<T> {
        fn new() -> OnceCell<T> { ... }
        fn set(&self, value: T) -> Result<(), T> { ... }
        fn get(&self) -> Option<&T> { ... }
    }
  2. Use Lazy<T> for macro-free lazy initialization

    master

    once_cell::sync::Lazy<T> is built on top of OnceCell and provides an API similar to the lazy_static! macro but without using macros. It is useful for defining global or static variables that are initialized only when first accessed.

    To use it, wrap your initialization logic in Lazy::new(|| { ... }).

    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());
    }
  3. Use `Lazy` for thread-safe, on-demand initialization

    master

    The Lazy<T, F> type provides a value that is initialized only upon its first access. It is thread-safe and suitable for use in static variables.

    Key Methods

    • Lazy::new(f): Creates a new lazy value with the provided initializing function f.
    • force(&self) -> &T: Explicitly forces evaluation and returns a reference. This is what happens automatically when using Deref.
    • force_mut(&mut self) -> &mut T: Forces evaluation and returns a mutable reference.
    • get(&self) -> Option<&T>: Returns a reference to the value if it has already been initialized, otherwise None.
    • get_mut(&mut self) -> Option<&mut T>: Returns a mutable reference if initialized, otherwise None.
    • into_value(self) -> Result<T, F>: Consumes the Lazy instance. Returns Ok(value) if initialized, or Err(f) if it was never initialized. Note: if the Lazy instance was previously 'poisoned' (e.g. via a failed force attempt), this may panic.

    Implementation Details

    Lazy implements Deref and DerefMut, so you can typically use it as if it were the underlying type T.

    use std::collections::HashMap;
    use once_cell::sync::Lazy;
    
    static HASHMAP: Lazy<HashMap<i32, String>> = Lazy::new(|| {
        println!("initializing");
        let mut m = HashMap::new();
        m.insert(13, "Spica".to_string());
        m.insert(74, "Hoyten".to_string());
        m
    });
    
    fn main() {
        println!("ready");
        std::thread::spawn(|| {
            println!("{:?}", HASHMAP.get(&13));
        }).join().unwrap();
        println!("{:?}", HASHMAP.get(&74));
    
        // Prints:
        //   ready
        //   initializing
        //   Some("Spica")
        //   Some("Hoyten")
    }
  4. Use Lazy for automatic value initialization on first access

    master

    The Lazy<T, F> type streamlines the pattern of initializing a value only when it is first used. It is essentially a replacement for the lazy_static! macro but works with local variables as well.

    • Global Data: Declare static instances of Lazy to initialize global data.
    • Local Data: Use Lazy within functions for general-purpose lazy evaluation.

    Note: When using Lazy for global data, declare the variable as static, not const.

    Unsync vs Sync: Use once_cell::unsync::Lazy for single-threaded code and once_cell::sync::Lazy for multi-threaded code.

    // Global lazy initialization
    use once_cell::sync::Lazy;
    use std::sync::Mutex;
    use std::collections::HashMap;
    
    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());
    }
  5. Handle initialization errors with `get_or_try_init`

    master

    Use get_or_try_init to initialize a OnceCell using a closure that returns a Result. If the cell is empty and the closure returns an error, the error is returned to the caller and the cell remains uninitialized.

    Key behaviors:

    • Panics: If the closure panics, the panic propagates and the cell remains uninitialized.
    • Reentrancy: Reentrantly initializing the cell from within the closure is an error and may result in a deadlock.
    use once_cell::sync::OnceCell;
    
    let cell = OnceCell::new();
    assert_eq!(cell.get_or_try_init(|| Err(())), Err(()));
    assert!(cell.get().is_none());
    
    let value = cell.get_or_try_init(|| -> Result<i32, ()> {
        Ok(92)
    });
    assert_eq!(value, Ok(&92));
    assert_eq!(cell.get(), Some(&92));
  6. Use sync::OnceCell for thread-safe one-time initialization

    master

    The sync::OnceCell<T> is a thread-safe, blocking version of OnceCell. It implements Sync and ensures that reading a non-None value establishes a happens-before relationship with the corresponding write.

    Key methods:

    • get(&self) -> Option<&T>: Returns the value immediately if initialized, otherwise returns None. This method never blocks.
    • wait(&self) -> &T: Blocks the current thread until the cell is initialized, then returns a reference to the value. (Requires std feature).
    • set(&self, value: T) -> Result<(), T>: Attempts to initialize the cell. Returns Ok(()) if successful, or Err(value) if already full.
    use once_cell::sync::OnceCell;
    
    static CELL: OnceCell<String> = OnceCell::new();
    
    assert!(CELL.get().is_none());
    
    std::thread::spawn(|| {
        assert_eq!(CELL.set("Hello".to_string()), Ok(()));
    }).join().unwrap();
    
    assert_eq!(CELL.get().unwrap(), "Hello");
  7. Use unsync::OnceCell for single-threaded one-time initialization

    master

    The unsync::OnceCell<T> is a non-thread-safe cell that can be written to at most once. It provides direct access to the stored contents via shared references (&T), which is more efficient than RefCell or Mutex because it doesn't require RAII guards.

    Use this in single-threaded contexts to store values that are computed once and then accessed many times.

    use once_cell::unsync::OnceCell;
    
    let cell = OnceCell::new();
    assert!(cell.get().is_none());
    
    let value: &String = cell.get_or_init(|| {
        "Hello, World!".to_string()
    });
    assert_eq!(value, "Hello, World!");
    assert!(cell.get().is_some());
  8. Initialize and retrieve values with `get_or_init`

    master

    Use get_or_init to retrieve the contents of a OnceCell, initializing it with the provided closure f if the cell is currently empty.

    Key behaviors:

    • Concurrency: Multiple threads may call get_or_init concurrently, but the initializing function is guaranteed to execute exactly once.
    • Panics: If the initializing function panics, the panic propagates to the caller and the cell remains uninitialized.
    • Reentrancy: Reentrantly initializing the cell from within the closure f is an error and may result in a deadlock.
    use once_cell::sync::OnceCell;
    
    let cell = OnceCell::new();
    let value = cell.get_or_init(|| 92);
    assert_eq!(value, &92);
    let value = cell.get_or_init(|| unreachable!());
    assert_eq!(value, &92);
  9. Attempt to insert a value with `try_insert`

    master

    The try_insert method attempts to set the cell's value. Unlike set, it returns a Result containing a reference to the final value.

    • Success: Returns Ok(&T) containing a reference to the newly inserted value.
    • Failure: Returns Err((&T, T)) where the first element is a reference to the existing value and the second element is the value that failed to be inserted (allowing the caller to reuse it).
    use once_cell::sync::OnceCell;
    
    let cell = OnceCell::new();
    assert!(cell.get().is_none());
    
    assert_eq!(cell.try_insert(92), Ok(&92));
    assert_eq!(cell.try_insert(62), Err((&92, 62)));
    
    assert!(cell.get().is_some());
  10. Reset or consume a `OnceCell` with `take` and `into_inner`

    master

    Depending on whether you have mutable or owned access, you can extract the value from a OnceCell:

    • take(&mut self) -> Option<T>: Moves the value out of the cell, returning it and leaving the cell in an uninitialized state. If the cell was already empty, it returns None and has no effect.
    • into_inner(self) -> Option<T>: Consumes the OnceCell and returns the wrapped value if it was initialized, or None if it was empty.
    use once_cell::sync::OnceCell;
    
    // Using take()
    let mut cell: OnceCell<String> = OnceCell::new();
    assert_eq!(cell.take(), None);
    
    let mut cell = OnceCell::new();
    cell.set("hello".to_string()).unwrap();
    assert_eq!(cell.take(), Some("hello".to_string()));
    assert_eq!(cell.get(), None);
    
    // Using into_inner()
    let cell: OnceCell<String> = OnceCell::new();
    assert_eq!(cell.into_inner(), None);
    
    let cell = OnceCell::new();
    cell.set("hello".to_string()).unwrap();
    assert_eq!(cell.into_inner(), Some("hello".to_string()));
  11. Compare OnceCell flavors with standard library types

    master

    Unsync Types

    TypeAccess ModeDrawbacks
    Cell<T>Trequires T: Copy for get
    RefCell<T>RefMut<T> / Ref<T>may panic at runtime
    unsync::OnceCell<T>&Tassignable only once

    Sync Types

    TypeAccess ModeDrawbacks
    AtomicTTworks only with certain Copy types
    Mutex<T>MutexGuard<T>may deadlock or block the thread
    sync::OnceCell<T>&Tassignable only once, may block the thread