How to Choose Between Rust’s LazyLock, LazyCell, OnceLock, and OnceCell
Rust 1.80 introduces LazyCell and LazyLock, synchronized lazy-initialization primitives that differ from OnceCell and OnceLock in thread safety and built‑in initialization, and this article explains their characteristics, trade‑offs, and provides concrete code examples to help developers select the appropriate type for their use case.
Rust 1.80.0 adds two new synchronous primitives, LazyCell and LazyLock, which delay data initialization until the first access. Unlike the earlier OnceCell and OnceLock, the initialization function is embedded in the cell itself. This stabilizes functionality that previously required the external lazy_static and once_cell crates, so developers can now rely on the standard library for lazy initialization.
Differences
LazyLock: Thread‑safe. The first thread that accesses the static value performs the one‑time initialization, and all threads subsequently see the same value. LazyCell: Not thread‑safe. It does not implement Sync, so it cannot be used in a normal static, but it works inside a thread_local! static where each thread gets its own independent initialization. OnceLock: Thread‑safe. It can be initialized only once; after a lock is acquired it cannot be re‑initialized, making it useful for global mutable state that must not be recreated. OnceCell: Not thread‑safe. The cell can be set a single time and then becomes immutable, typically used for lazily initialized global variables.
In summary, LazyLock and LazyCell provide a built‑in initialization function, whereas OnceLock and OnceCell require manual initialization. Choose the type that matches your thread‑safety requirements and whether you need an automatic initializer.
LazyLock
LazyLockis thread‑safe, making it suitable for static values that must be shared across threads. In the example below, a spawned thread and the main thread both observe the same elapsed time because the static LAZY_TIME is initialized exactly once by whichever thread accesses it first. This differs from OnceLock::get_or_init(), which requires an explicit call to perform initialization.
use std::sync::LazyLock;
use std::time::Instant;
// The initializer runs only on the first access.
static LAZY_TIME: LazyLock<Instant> = LazyLock::new(Instant::now);
fn main() {
let start = Instant::now();
std::thread::scope(|s| {
s.spawn(|| {
println!("child thread lazy time is {:?}", LAZY_TIME.duration_since(start));
});
println!("main thread lazy time is {:?}", LAZY_TIME.duration_since(start));
});
}LazyCell
LazyCelloperates without thread synchronization and therefore does not implement Sync. It can still be used inside a thread_local! static, giving each thread its own separate initialization.
fn main() {
use std::cell::LazyCell;
let lazy: LazyCell<i32> = LazyCell::new(|| {
println!("initializing");
92
});
println!("ready");
println!("{}", *lazy);
println!("{}", *lazy);
// Prints:
// ready
// initializing
// 92
// 92
}OnceLock
OnceLockprovides a lock that can be initialized only once. It offers methods such as get, get_or_init, and set. The example demonstrates a static OnceLock that is lazily initialized by a child thread; subsequent accesses return the stored value.
#![allow(unused)]
fn main() {
use std::sync::OnceLock;
static CELL: OnceLock<usize> = OnceLock::new();
// `OnceLock` is not yet initialized.
assert!(CELL.get().is_none());
// Spawn a thread that attempts initialization.
std::thread::spawn(|| {
let value = CELL.get_or_init(|| 12345);
assert_eq!(value, &12345);
})
.join()
.unwrap();
// `OnceLock` is now initialized and can be read.
assert_eq!(CELL.get(), Some(&12345));
}OnceCell
OnceCellis a non‑thread‑safe cell that can be set exactly once. Its primary methods are set, get, and get_or_init. The following code shows a OnceCell used to lazily create a String value.
fn main() {
use std::cell::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());
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
BirdNest Tech Talk
Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
