Interior Mutability

LazyLock<T>

std::sync::LazyLock<T, F = fn() -> T> (stabilized in Rust 1.80) is the thread-safe counterpart to LazyCell<T>. It pairs a const-constructible synchronization state with an initialization function F that executes automatically on the first dereference (*lazy) from any thread, replacing the historical lazy_static! macro in standard Rust.

How LazyLock<T> Works in static Declarations

Because static items in Rust must be initialized with compile-time const expressions, heap-allocated types such as HashMap<K, V>, compiled regular expressions, or environment-derived structs cannot be constructed directly in a static initializer. LazyLock::new(|| ...) is a const fn that stores the initialization function pointer at compile time and defers executing it until the static item is first dereferenced at runtime:

static REGISTRY: LazyLock<HashMap<...>> = LazyLock::new(|| { ... });

  Compile Time:
    Stores function pointer in static data segment (0 heap allocations at startup)

  First Runtime Deref (`REGISTRY.get("key")`):
    Thread acquires internal `Once` lock -> runs closure -> stores `HashMap` -> releases lock

  All Subsequent Derefs Across Any Thread:
    Fast atomic load -> returns `&HashMap` via `Deref`
use std::collections::HashMap;
use std::sync::LazyLock;
use std::thread;

static MIME_TYPES: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
    println!("  [initializing MIME_TYPES static table once]");
    HashMap::from([
        ("rs", "text/x-rust"),
        ("json", "application/json"),
        ("wasm", "application/wasm"),
    ])
});

fn main() {
    println!("main started (MIME_TYPES not yet initialized)");

    thread::scope(|s| {
        s.spawn(|| {
            println!("thread 1 lookup .rs:   {:?}", MIME_TYPES.get("rs"));
        });
        s.spawn(|| {
            println!("thread 2 lookup .wasm: {:?}", MIME_TYPES.get("wasm"));
        });
    });
}
main started (MIME_TYPES not yet initialized)
  [initializing MIME_TYPES static table once]
thread 1 lookup .rs:   Some("text/x-rust")
thread 2 lookup .wasm: Some("application/wasm")

Comparing OnceCell, LazyCell, OnceLock, and LazyLock

Type Thread Safe (Sync)? Initializer Provided At Implements Deref<Target = T>? Typical Use Case
OnceCell<T> No (!Sync) Access time (.get_or_init / .set) No Single-threaded lazy struct fields that depend on &self
LazyCell<T, F> No (!Sync) Construction time (LazyCell::new(f)) Yes Single-threaded local lazy values with fixed initializer
OnceLock<T> Yes (Sync if T: Send + Sync) Access time (.get_or_init / .set) No Global static or shared struct field initialized with runtime inputs
LazyLock<T, F> Yes (Sync if T: Send + Sync, F: Send) Construction time (LazyLock::new(f)) Yes Self-contained static globals (lookup tables, regexes, thread pools)

Explicit Forcing with LazyLock::force

When you want to trigger initialization eagerly (for example, during application warm-up before serving traffic) without calling a method on T, LazyLock::force(&STATIC_ITEM) initializes the value and returns &T.