Quick Contact

✉
fahimkhan20148@gmail.com
📱
+971 507 286 133
Back to Notes
August 18, 2026
rustconcurrencyatomicslazy-initialization

Lazy Initialization in Rust

Lazy initialization is a pattern where a constant or expensive resource is calculated only when first requested and then stored for subsequent use.

In concurrent programs, lazy initialization must be synchronized to prevent race conditions where multiple threads attempt initialization simultaneously.

use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering::Relaxed;

fn get_x() -> u64 {
	static X: AtomicU64 = AtomicU64::new(0);
	let mut x = X.load(Relaxed);
	if x == 0 {
		x = calculate_x();
		X.store(x, Relaxed);
	}
	x
}

If X is supposed to be a constant that doesn't change throughout the run of the program, we can calculate it; it takes time and compute. We would prefer the first thread to call it, calculate it, and store the value for later use. The above code looks correct but has one flaw. If two threads call get_x, they will both see x == 0 and restart the computation, whichever finishes first. It's called a race condition. To fix this, we have std::sync::Once and std::sync::OnceLock; these are atomic operations.

Once

use std::sync::Once;
use std::thread;

static INIT: Once = Once::new();

fn init() {
	INIT.call_once(|| {
		println!("in init");
	});
}

fn main() {
	thread::scope(|s| {
		s.spawn(|| {
			init(); // prints "in init"
		});
		
		s.spawn(|| {
			init(); // does nothing; sleeps till first thread finishes
		});
	});
}

OnceLock

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

#[derive(Clone)]
struct Config {}

static GLOBAL_CONFIG: OnceLock<Config> = OnceLock::new();

fn get_config() -> Config {
	todo!();
}

fn main() {
	thread::scope(|s| {
		s.spawn(|| {
			let config = GLOBAL_CONFIG.get_or_init(|| get_config()).clone(); // load the config if not loaded
		});

		s.spawn(|| {
			let config = GLOBAL_CONFIG.get_or_init(|| get_config()).clone(); // sleeps till the first thread loads the config
		});
	});
}