Synchronization Primitives
LazyLock<T>
std::sync::LazyLock<T, F = fn() -> T> is a thread-safe lazy initialization primitive that bundles an initialization function F directly into the container at construction time (LazyLock::new(|| ...)) and executes it automatically on the first dereference (*lazy or LazyLock::force(&lazy)).
How LazyLock<T, F> Combines Once with a Union of F and T
Internally, LazyLock<T, F> stores a std::sync::Once state word alongside a union holding either the uncalled initializer ManuallyDrop<F> (before initialization) or the initialized value ManuallyDrop<T> (after initialization). When the first thread dereferences the LazyLock:
- The
Oncelock ensures only one thread runsF. Fis moved out of the union and invoked (f()), dropping any captured state inF.- The resulting
Tis written into the union, and all subsequentDerefaccesses return&Tvia a fast atomic load.
LazyLock<T, F> Memory Layout Transition:
Before First Dereference: After First Dereference:
+----------------------------+ +----------------------------+
| once: Once (Incomplete) | ----> | once: Once (Complete) |
| data: Union::Func(f: F) | | data: Union::Value(val: T) |
+----------------------------+ +----------------------------+
Choosing Between LazyLock<T> and OnceLock<T>
| Question | Use LazyLock<T> |
Use OnceLock<T> |
|---|---|---|
Is the initialization logic known at static declaration time? |
Yes (static TABLE: LazyLock<...> = LazyLock::new(|| ...);) |
No (depends on CLI args or config file loaded in main()) |
| How do callers access the value? | Transparently via Deref (TABLE.get("key"), &*TABLE) |
Explicitly via .get() -> Option<&T> or .get_or_init(|| ...) |
| Can initialization fail and be retried later? | No (if closure panics, LazyLock is poisoned) |
Yes (check condition before calling .set(val)) |
Using LazyLock<T> for Thread-Safe Static Caches and Regex/Lookup Tables
use std::collections::HashMap;
use std::sync::LazyLock;
use std::thread;
static STATUS_MESSAGES: LazyLock<HashMap<u16, &'static str>> = LazyLock::new(|| {
println!(" [building STATUS_MESSAGES table on first access]");
HashMap::from([
(200, "OK"),
(404, "Not Found"),
(500, "Internal Server Error"),
])
});
fn format_response(code: u16) -> String {
// Dereferences `STATUS_MESSAGES` directly as `&HashMap<u16, &'static str>`:
let reason = STATUS_MESSAGES.get(&code).copied().unwrap_or("Unknown");
format!("HTTP/1.1 {code} {reason}")
}
fn main() {
// Optionally force initialization during startup warm-up, or let first worker trigger it:
thread::scope(|s| {
s.spawn(|| println!("{}", format_response(200)));
s.spawn(|| println!("{}", format_response(404)));
});
}
[building STATUS_MESSAGES table on first access]
HTTP/1.1 200 OK
HTTP/1.1 404 Not Found