LazyCell<T>
std::cell::LazyCell<T, F = fn() -> T> (stabilized in Rust 1.80) is a single-threaded lazy wrapper that bundles an initialization closure F directly with a write-once storage slot and automatically runs the closure the first time the cell is dereferenced (*lazy or LazyCell::force(&lazy)).
LazyCell<T, F> vs. OnceCell<T>
While OnceCell<T> starts empty and requires passing an initialization closure to .get_or_init(|| ...) at the point of access, LazyCell<T, F> takes the initialization closure at construction time (LazyCell::new(|| ...)) and implements Deref<Target = T>, so callers access it like a normal value or smart pointer.
LazyCell<T, F> Internal State Transition:
Before First Deref: On First Deref (`*lazy`):
+---------------------------+ +---------------------------+
| State::Uninit(f: F) | ----> | Runs `let val = f();` |
+---------------------------+ | Transitions to: |
| State::Init(val: T) |
+-------------+-------------+
|
v
Subsequent Derefs return `&val` directly
| Feature | OnceCell<T> |
LazyCell<T, F> |
|---|---|---|
| Where initializer is provided | At access time (.get_or_init(f)) or .set(val) |
At construction time (LazyCell::new(f)) |
| Can different call sites pass different initializers? | Yes | No (single fixed initializer F) |
Can initializer access &self of enclosing struct? |
Yes (self.cell.get_or_init(|| self.other_field)) |
Only variables captured when LazyCell was constructed |
Implements Deref<Target = T>? |
No (use .get() or .get_or_init()) |
Yes (*lazy and .method() initialize automatically) |
Using LazyCell<T> for On-Demand Computation
use std::cell::LazyCell;
use std::collections::HashMap;
fn main() {
let multiplier = 10;
let lookup = LazyCell::new(|| {
println!(" [initializing lookup table...]");
HashMap::from([
("alpha", 1 * multiplier),
("beta", 2 * multiplier),
("gamma", 3 * multiplier),
])
});
println!("lookup created (not yet initialized)");
// First dereference runs the closure:
println!("alpha = {}", lookup["alpha"]);
// Second dereference reuses the initialized HashMap:
println!("gamma = {}", lookup["gamma"]);
}
lookup created (not yet initialized)
[initializing lookup table...]
alpha = 10
gamma = 30
Storing LazyCell<T> Inside a Struct
Because every closure in Rust has a unique anonymous type, storing a LazyCell in a struct field defaults the second type parameter F to a function pointer fn() -> T (LazyCell<T> is an alias for LazyCell<T, fn() -> T>), or uses Box<dyn FnOnce() -> T> when capturing environment state:
use std::cell::LazyCell;
struct Environment {
// Uses the default `F = fn() -> String` type parameter:
hostname: LazyCell<String>,
}
fn default_hostname() -> String {
println!(" [resolving hostname...]");
String::from("node-01.internal")
}
fn main() {
let env = Environment {
hostname: LazyCell::new(default_hostname),
};
println!("before access");
println!("host: {}", &*env.hostname);
println!("host again: {}", &*env.hostname);
}
before access
[resolving hostname...]
host: node-01.internal
host again: node-01.internal
Re-Entrancy Panic Poisoning
If the initialization closure of a LazyCell recursively dereferences the same LazyCell before initialization completes (or if the initialization closure panics), LazyCell transitions to a poisoned state and panics with "LazyCell instance has previously been poisoned" on subsequent accesses.