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>
- Const construction:
OnceLock::new()is aconst fn, sostatic CONFIG: OnceLock<AppConfig> = OnceLock::new();requires no external crates (lazy_staticoronce_cell) or unsafestatic mut. - Thread-safe one-time initialization: If multiple threads call
.get_or_init(f)concurrently on an uninitializedOnceLock<T>, the internalstd::sync::Onceprimitive blocks the contending threads until the winning thread's closuref()finishes writingT. - Wait-free reads after initialization: Once initialized,
.get()and.get_or_init()perform a single atomicAcquireload of the state flag and return&Tdirectly 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