Interior Mutability

OnceLock<T>

std::sync::OnceLock<T> (stabilized in Rust 1.70) is the thread-safe counterpart to OnceCell<T>. It is a write-once synchronization primitive that can be stored in static variables or shared across threads via &OnceLock<T> (Sync when T: Send + Sync), guaranteeing that at most one thread initializes the inner value T and all subsequent callers receive a direct, lock-free shared reference &T.

Synchronization Guarantees of OnceLock<T>

  1. Const construction: OnceLock::new() is a const fn, so static CONFIG: OnceLock<AppConfig> = OnceLock::new(); requires no external crates (lazy_static or once_cell) or unsafe static mut.
  2. Thread-safe one-time initialization: If multiple threads call .get_or_init(f) concurrently on an uninitialized OnceLock<T>, the internal std::sync::Once primitive blocks the contending threads until the winning thread's closure f() finishes writing T.
  3. Wait-free reads after initialization: Once initialized, .get() and .get_or_init() perform a single atomic Acquire load of the state flag and return &T directly without acquiring any mutex or guard.
Concurrent Initialization of `OnceLock<T>`:

  Thread 1: .get_or_init(init_fn) ---> Wins race, executes `init_fn()`, writes `T`
                                                     |
  Thread 2: .get_or_init(init_fn) ---> Waits --------+---> Receives `&T`
                                                     |
  Thread 3 (later): .get() ------------(Atomic load)-+---> Receives `Some(&T)`

Core OnceLock<T> Methods

Method Signature Behavior
OnceLock::new() const fn new() -> OnceLock<T> Creates an empty OnceLock<T>
.get() fn get(&self) -> Option<&T> Returns Some(&T) if initialized, or None
.set(val) fn set(&self, value: T) -> Result<(), T> Sets value if uninitialized; returns Err(value) if already set
.get_or_init(f) fn get_or_init(&self, f: impl FnOnce() -> T) -> &T Initializes with f() if empty (blocking concurrent initializers) and returns &T
.get_mut() fn get_mut(&mut self) -> Option<&mut T> Exclusive mutable access when holding &mut OnceLock<T>
.take() fn take(&mut self) -> Option<T> Moves out the value and resets the lock (requires &mut OnceLock<T>)

Global Runtime Configuration with static OnceLock<T>

OnceLock<T> is the standard pattern for global configuration that must be initialized with runtime arguments (such as CLI flags or environment variables) early in main() and then read as &'static T from any thread:

use std::sync::OnceLock;
use std::thread;

#[derive(Debug)]
struct GlobalConfig {
    region: String,
    max_connections: usize,
}

static CONFIG: OnceLock<GlobalConfig> = OnceLock::new();

fn config() -> &'static GlobalConfig {
    CONFIG.get().expect("CONFIG must be initialized in main()")
}

fn main() {
    CONFIG
        .set(GlobalConfig {
            region: String::from("ap-south-1"),
            max_connections: 256,
        })
        .expect("CONFIG initialized more than once");

    thread::scope(|s| {
        for worker in 0..2 {
            s.spawn(move || {
                println!(
                    "worker {} reading region={}, max_conn={}",
                    worker,
                    config().region,
                    config().max_connections
                );
            });
        }
    });
}
worker 0 reading region=ap-south-1, max_conn=256
worker 1 reading region=ap-south-1, max_conn=256