Loading page…
Rust walkthroughs
Loading page…
Once is a synchronization primitive that ensures a piece of initialization code is executed exactly once, even when called from multiple threads. It's useful for lazy initialization of global state, singleton patterns, and ensuring thread-safe one-time setup.
Key concepts:
call_once safelyWhen to use Once:
When NOT to use Once:
Option with Mutex)use std::sync::Once;
use std::thread;
static INIT: Once = Once::new();
fn expensive_setup() {
println!("Running expensive setup...");
thread::sleep(std::time::Duration::from_millis(100));
println!("Setup complete!");
}
fn main() {
let mut handles = vec![];
for i in 0..5 {
let handle = thread::spawn(move || {
println!("Thread {} starting", i);
INIT.call_once(|| {
expensive_setup();
});
println!("Thread {} proceeding", i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}use std::sync::OnceLock;
use std::thread;
static CONFIG: OnceLock<Vec<String>> = OnceLock::new();
fn get_config() -> &'static Vec<String> {
CONFIG.get_or_init(|| {
println!("Initializing config...");
vec![
String::from("host=localhost"),
String::from("port=8080"),
String::from("debug=true"),
]
})
}
fn main() {
let mut handles = vec![];
for i in 0..3 {
let handle = thread::spawn(move || {
let config = get_config();
println!("Thread {} got config: {:?}", i, config);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}use std::sync::LazyLock;
use std::thread;
static DATABASE_URL: LazyLock<String> = LazyLock::new(|| {
println!("Building database URL...");
String::from("postgres://user:pass@localhost/db")
});
static NUMBERS: LazyLock<Vec<i32>> = LazyLock::new(|| {
println!("Generating numbers...");
(1..=100).collect()
});
fn main() {
let mut handles = vec![];
for i in 0..3 {
let handle = thread::spawn(move || {
println!("Thread {} accessing...", i);
println!(" URL: {}", *DATABASE_URL);
println!(" Sum: {}", NUMBERS.iter().sum::<i32>());
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}use std::sync::{OnceLock, Mutex};
use std::thread;
struct Database {
connections: Mutex<u32>,
}
impl Database {
fn new() -> Self {
println!("Creating database singleton...");
Self {
connections: Mutex::new(0),
}
}
fn connect(&self) {
let mut conns = self.connections.lock().unwrap();
*conns += 1;
println!("Connection count: {}", *conns);
}
}
static DB: OnceLock<Database> = OnceLock::new();
fn get_db() -> &'static Database {
DB.get_or_init(Database::new)
}
fn main() {
let mut handles = vec![];
for i in 0..3 {
let handle = thread::spawn(move || {
let db = get_db();
println!("Thread {} connecting", i);
db.connect();
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}use std::sync::{Once, OnceLock};
use std::thread;
static ONCE: Once = Once::new();
static ONCE_LOCK: OnceLock<String> = OnceLock::new();
fn main() {
// Once: for side effects, no return value
ONCE.call_once(|| {
println!("One-time setup with Once");
});
// OnceLock: for initializing a value
let value = ONCE_LOCK.get_or_init(|| {
println!("One-time init with OnceLock");
String::from("Initialized value")
});
println!("Value: {}", value);
// Subsequent calls don't re-initialize
let value2 = ONCE_LOCK.get_or_init(|| {
panic!("This won't run!");
});
println!("Same value: {}", value2);
}use std::sync::OnceLock;
use std::fs;
static FILE_CONTENTS: OnceLock<String> = OnceLock::new();
fn get_file_contents() -> Result<&'static str, std::io::Error> {
FILE_CONTENTS.get_or_try_init(|| {
fs::read_to_string("config.txt")
.or_else(|_| Ok(String::from("default config")))
}).map(|s| s.as_str())
}
fn main() {
match get_file_contents() {
Ok(contents) => println!("Contents: {}", contents),
Err(e) => println!("Error: {}", e),
}
}use std::sync::Once;
use std::sync::Mutex;
use std::thread;
use std::io::Write;
static LOGGER_INIT: Once = Once::new();
static LOG_MUTEX: Mutex<()> = Mutex::new(());
fn log(message: &str) {
LOGGER_INIT.call_once(|| {
// One-time logger setup
eprintln!("[LOGGER] Initialized");
});
let _guard = LOG_MUTEX.lock().unwrap();
eprintln!("[LOG] {}", message);
}
fn main() {
let mut handles = vec![];
for i in 0..5 {
let handle = thread::spawn(move || {
log(&format!("Message from thread {}", i));
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}use std::sync::OnceLock;
use std::thread;
struct ExpensiveComputation {
data: Vec<u64>,
}
impl ExpensiveComputation {
fn compute() -> Self {
println!("Computing expensive data...");
thread::sleep(std::time::Duration::from_millis(200));
let data: Vec<u64> = (0..1000).map(|x| x * x).collect();
Self { data }
}
}
static COMPUTATION: OnceLock<ExpensiveComputation> = OnceLock::new();
fn get_computation() -> &'static ExpensiveComputation {
COMPUTATION.get_or_init(ExpensiveComputation::compute)
}
fn main() {
let mut handles = vec![];
for i in 0..3 {
let handle = thread::spawn(move || {
let comp = get_computation();
let sum: u64 = comp.data.iter().sum();
println!("Thread {} sum: {}", i, sum);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Once;
use std::thread;
static INIT: Once = Once::new();
static INITIALIZED: AtomicBool = AtomicBool::new(false);
fn ensure_initialized() {
INIT.call_once(|| {
println!("Initializing...");
thread::sleep(std::time::Duration::from_millis(100));
INITIALIZED.store(true, Ordering::SeqCst);
println!("Done!");
});
}
fn is_initialized() -> bool {
INITIALIZED.load(Ordering::SeqCst)
}
fn main() {
println!("Before: initialized = {}", is_initialized());
ensure_initialized();
println!("After: initialized = {}", is_initialized());
// Calling again does nothing
ensure_initialized();
println!("Final: initialized = {}", is_initialized());
}use std::sync::OnceLock;
use std::collections::HashMap;
#[derive(Debug)]
struct Config {
settings: HashMap<String, String>,
}
impl Config {
fn load() -> Self {
println!("Loading configuration...");
let mut settings = HashMap::new();
settings.insert("host".to_string(), "localhost".to_string());
settings.insert("port".to_string(), "8080".to_string());
settings.insert("debug".to_string(), "true".to_string());
Self { settings }
}
fn get(&self, key: &str) -> Option<&String> {
self.settings.get(key)
}
}
static CONFIG: OnceLock<Config> = OnceLock::new();
fn config() -> &'static Config {
CONFIG.get_or_init(Config::load)
}
fn main() {
println!("Host: {:?}", config().get("host"));
println!("Port: {:?}", config().get("port"));
println!("Debug: {:?}", config().get("debug"));
}use std::sync::{Once, OnceLock};
use std::thread;
static DB_INIT: Once = Once::new();
static CACHE_INIT: Once = Once::new();
static LOGGER: OnceLock<String> = OnceLock::new();
fn init_database() {
DB_INIT.call_once(|| {
println!("Database initialized");
});
}
fn init_cache() {
CACHE_INIT.call_once(|| {
println!("Cache initialized");
});
}
fn get_logger() -> &'static str {
LOGGER.get_or_init(|| {
println!("Logger initialized");
String::from("app_logger")
})
}
fn main() {
init_database();
init_cache();
println!("Logger: {}", get_logger());
// Subsequent calls are no-ops
init_database();
init_cache();
println!("Logger: {}", get_logger());
}use std::sync::{OnceLock, Mutex};
use std::thread;
struct Counter {
value: Mutex<i32>,
}
impl Counter {
fn new() -> Self {
println!("Creating counter...");
Self {
value: Mutex::new(0),
}
}
fn increment(&self) -> i32 {
let mut v = self.value.lock().unwrap();
*v += 1;
*v
}
}
static COUNTER: OnceLock<Counter> = OnceLock::new();
fn main() {
let mut handles = vec![];
for i in 0..5 {
let handle = thread::spawn(move || {
let counter = COUNTER.get_or_init(Counter::new);
let value = counter.increment();
println!("Thread {} incremented to {}", i, value);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}// Note: lazy_static requires the crate, OnceLock is in std
use std::sync::OnceLock;
// With OnceLock (standard library):
static LAZY_VALUE: OnceLock<Vec<i32>> = OnceLock::new();
fn get_lazy_value() -> &'static Vec<i32> {
LAZY_VALUE.get_or_init(|| {
println!("Computing lazy value...");
(1..=10).collect()
})
}
fn main() {
println!("Before access");
println!("Value: {:?}", get_lazy_value());
println!("After first access");
println!("Value: {:?}", get_lazy_value()); // No re-computation
}
// With lazy_static crate (commented):
// #[macro_use]
// extern crate lazy_static;
//
// lazy_static! {
// static ref LAZY_VALUE: Vec<i32> = {
// println!("Computing...");
// (1..=10).collect()
// };
// }use std::sync::{Once, Mutex};
use std::collections::HashMap;
type PluginFn = fn() -> String;
static PLUGIN_INIT: Once = Once::new();
static PLUGINS: Mutex<HashMap<String, PluginFn>> = Mutex::new(HashMap::new());
fn register_plugins() {
PLUGIN_INIT.call_once(|| {
let mut plugins = PLUGINS.lock().unwrap();
plugins.insert("greet".to_string(), greet_plugin);
plugins.insert("farewell".to_string(), farewell_plugin);
println!("Plugins registered");
});
}
fn greet_plugin() -> String {
String::from("Hello, World!")
}
fn farewell_plugin() -> String {
String::from("Goodbye, World!")
}
fn run_plugin(name: &str) -> Option<String> {
register_plugins();
let plugins = PLUGINS.lock().unwrap();
plugins.get(name).map(|f| f())
}
fn main() {
println!("{:?}", run_plugin("greet"));
println!("{:?}", run_plugin("farewell"));
println!("{:?}", run_plugin("unknown"));
}use std::sync::OnceLock;
use std::thread;
use std::time::Duration;
struct Service {
name: String,
endpoint: String,
timeout: Duration,
}
impl Service {
fn new(name: &str, endpoint: &str, timeout_ms: u64) -> Self {
println!("Creating service: {}", name);
Self {
name: name.to_string(),
endpoint: endpoint.to_string(),
timeout: Duration::from_millis(timeout_ms),
}
}
}
static API_SERVICE: OnceLock<Service> = OnceLock::new();
static DB_SERVICE: OnceLock<Service> = OnceLock::new();
fn get_api_service() -> &'static Service {
API_SERVICE.get_or_init(|| Service::new("api", "https://api.example.com", 5000))
}
fn get_db_service() -> &'static Service {
DB_SERVICE.get_or_init(|| Service::new("db", "postgres://localhost/db", 10000))
}
fn main() {
println!("API: {}", get_api_service().endpoint);
println!("DB: {}", get_db_service().endpoint);
}Once Types Comparison:
| Type | Rust Version | Purpose | Returns Value |
|------|--------------|---------|---------------|
| Once | Stable | One-time execution (side effects) | No |
| OnceLock<T> | 1.70+ | One-time value initialization | Yes |
| LazyLock<T> | 1.80+ | Lazy static initialization | Yes |
Once Methods:
| Method | Description |
|--------|-------------|
| new() | Create new Once instance |
| call_once(f) | Execute f exactly once |
| is_completed() | Check if initialization completed |
OnceLock Methods:
| Method | Description |
|--------|-------------|
| new() | Create new OnceLock instance |
| get() | Get reference if initialized |
| get_or_init(f) | Initialize if needed, return reference |
| get_or_try_init(f) | Initialize with fallible closure |
| set(value) | Set value if not already set |
| into_inner(self) | Extract inner value |
Common Patterns:
| Pattern | Type | Use Case |
|---------|------|----------|
| Singleton | OnceLock<T> | Single global instance |
| Lazy config | OnceLock<Config> | Load config on first access |
| Logger init | Once | One-time logger setup |
| Plugin registry | Once + Mutex<HashMap> | One-time registration |
| Service locator | OnceLock<Service> | Lazy service creation |
Key Points:
Once for side effects, OnceLock<T> for valuescall_once blocks until completionOnceLock::get_or_init returns &TLazyLock is the modern replacement for lazy_static!get_or_try_init for fallible initializationMutex for mutable global state