Global Statics and Lazy Initialization
Recipe | Crates | Categories |
---|---|---|
std | ||
once_cell | ||
lazy_static | ||
Declare Lazily Evaluated Constants |
- Immutable Global: use the
static
keyword (compile-time init, limited). - Lazy Init:
lazy_static
⮳ (runtime init, simple),once_cell
⮳ (runtime init, more control). - Mutable Global:
parking_lot::Mutex
⮳/RwLock
(thread-safe). - Thread-Local:
std::thread_local
. - Atomics:
std::sync::atomic
⮳.
Two key libraries:
once_cell
⮳: newer crate with more ergonomic API. Should be preferred for all new projects.lazy_static
⮳: older crate. Its API is less convenient, but crate is stable and maintained.
Prefer once_cell
⮳ over lazy_static. Use mutexes / rwlocks for mutable globals. Consider alternatives to globals.
The core functionality of once_cell
⮳ is now included in the standard library with the remaining parts on track to be stabilized in future.
std
OnceCell
⮳ is a cell which can be written to only once.
The corresponding Sync
version of OnceCell<T>
is OnceLock<T>
.
use std::cell::OnceCell; fn main() { let cell = OnceCell::new(); assert!(cell.get().is_none()); let value: &String = cell.get_or_init(|| "Hello, World!".to_string()); println!("{value}"); assert_eq!(value, "Hello, World!"); assert!(cell.get().is_some()); }
once_cell
once_cell
⮳ provides two cell-like types, unsync::OnceCell
and sync::OnceCell
. A OnceCell
might store arbitrary non-Copy types, can be assigned to at most once and provides direct access to the stored contents. The sync
flavor is thread-safe. once_cell
⮳ also has a once_cell::sync::Lazy
⮳ type, build on top of OnceCell
⮳:
use std::collections::HashMap; use std::sync::Mutex; use once_cell::sync::Lazy; // Must be static, not const 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()); }
lazy_static
use std::collections::HashMap; use std::sync::Mutex; use std::sync::MutexGuard; use anyhow::anyhow; use lazy_static::lazy_static; // `lazy_static` allows you to define statically initialized values that are // computed lazily at runtime. It can be particularly useful for initializing // data that is expensive to compute or that needs to be shared across multiple // threads. lazy_static! { // - Any type within the macro needs to fulfill the Sync trait. // - If a type has a destructor, it will not run when the process exits. // - The `static ref` keywords are passed to the macro, they are not part of the language per se. // CONFIG is a lazily-initialized HashMap that holds configuration data. static ref CONFIG: HashMap<String, String> = { let mut m = HashMap::new(); m.insert("host".to_string(), "localhost".to_string()); m.insert("port".to_string(), "8080".to_string()); m }; // You can call a method or a function: static ref COUNT: usize = CONFIG.len(); // COUNTER is a lazily-initialized Mutex-protected integer counter. // The Mutex ensures that the COUNTER can be safely incremented from multiple threads, if needed. static ref COUNTER: Mutex<i32> = Mutex::new(0); } fn main() -> anyhow::Result<()> { // Accessing the CONFIG. println!( "Host: {}", CONFIG.get("host").ok_or(anyhow!("No host key"))? ); println!( "Port: {}", CONFIG.get("port").ok_or(anyhow!("No port key"))? ); // The macro generates a unique type that implements `Deref<TYPE>` and // stores it in a static with name `NAME`. Here, we use `*` to dereference // and retrieve the inner value. println!("Count: {}", *COUNT); // Working with the COUNTER: // The (mutable) shared state within the Mutex can only be accessed once the // lock is held. Our non-atomic increment is safe because we're the only // thread which can access the shared state when the lock is held. // We `unwrap()` the return value to assert that we are not expecting // threads to ever fail while holding the lock. { let mut counter: MutexGuard<'_, i32> = COUNTER.lock().unwrap(); *counter += 1; println!("Counter: {}", *counter); } // The lock is unlocked here // And again, on a different thread: std::thread::spawn(move || { let lock_result = COUNTER.try_lock(); // If the lock cannot be acquired at this time (or is poisoned), // `Err` is returned. if let Ok(mut counter) = lock_result { *counter += 1; println!("Counter: {}", *counter); } else { println!("try_lock failed"); } }) .join() .expect("thread::spawn failed"); Ok(()) }
Declare Lazily Evaluated Constants
Declares a lazily evaluated constant std::collections::HashMap
⮳. The std::collections::HashMap
⮳ will be evaluated once and stored behind a global static reference.
use std::collections::HashMap; use lazy_static::lazy_static; lazy_static! { static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = { let mut map = HashMap::new(); map.insert("James", vec!["user", "admin"]); map.insert("Jim", vec!["user"]); map }; } fn show_access(name: &str) { let access = PRIVILEGES.get(name); println!("{}: {:?}", name, access); } fn main() { let access = PRIVILEGES.get("James"); println!("James: {:?}", access); show_access("Jim"); }