In Rust, initializing global or static data that requires runtime computation or is not `const` can be challenging due to the language's strict rules around statics. The `once_cell` library provides a robust and thread-safe solution for one-time, lazy initialization of such data.
The `once_cell` crate offers two primary types for this purpose: `OnceCell` and `Lazy`.
OnceCell<T>
The `OnceCell<T>` type is a thread-safe, single-assignment container. It can be initialized at most once. Subsequent attempts to set a value will either fail (returning `Err` if using `set`) or panic if `set` is called again with a different value (or `unwrap` on the `Result`). It's particularly useful when you need more explicit control over the initialization process (e.g., initializing from a specific function call) or when the type `T` doesn't necessarily implement `Sync` (though the `sync` module's `OnceCell` *does* require `T: Sync + Send`).
Key methods for `OnceCell`:
- `new()`: Creates an empty `OnceCell`.
- `set(value: T) -> Result<(), T>`: Attempts to set the value. Returns `Ok(())` on success, or `Err(value)` if the cell was already initialized.
- `get() -> Option<&T>`: Returns a reference to the contained value if it has been initialized, otherwise `None`.
- `get_or_init(f: impl FnOnce() -> T) -> &T`: Returns a reference to the contained value, initializing it with the result of `f` if it hasn't been initialized yet. This is an atomic operation.
Lazy<T, F = impl FnOnce() -> T>
The `Lazy<T, F>` type offers a more ergonomic way to perform lazy initialization, especially for `static` items. It automatically initializes its inner value the first time it is dereferenced. It stores a closure `F` that produces the value `T`, and this closure is executed only once, on demand.
Its mechanism is simple: when a `Lazy` static is accessed for the first time, it checks if its inner `OnceCell` is initialized. If not, it executes the stored closure to produce the value, initializes the `OnceCell`, and then returns a reference to the value. Subsequent accesses simply return the stored reference without re-executing the closure.
`Lazy` is ideal for static variables that are expensive to compute or need to be initialized at runtime, where you want the initialization to happen seamlessly on first use without explicit `get_or_init` calls. It typically requires `T: Sync + Send` and `F: Sync + Send` for use with statics.
Benefits
Both `OnceCell` and `Lazy` are fully thread-safe (when using the `sync` variants). They eliminate the need for manual locking or `unsafe` blocks for common lazy initialization patterns, providing a safe and idiomatic way to manage global state in Rust. While `std::lazy::OnceCell` and `std::lazy::SyncLazy` are now stable in the standard library (inspired by `once_cell`), the `once_cell` crate still offers `unsync` variants (`OnceCell` and `Lazy` without `Sync` bounds) and remains widely used, especially for older projects or when targeting specific Rust versions or requiring the `unsync` features.
Example Code
```rust
// Cargo.toml
// [dependencies]
// once_cell = "1.19.0"
use once_cell::sync::{OnceCell, Lazy};
use std::thread;
use std::time::Duration;
// --- Example 1: Using Lazy for a static, lazily initialized vector ---
// This static vector will be initialized only once, the first time it's accessed.
static MY_LAZY_VECTOR: Lazy<Vec<i32>> = Lazy::new(|| {
println!("Initializing MY_LAZY_VECTOR...");
// Simulate some expensive computation
thread::sleep(Duration::from_millis(100));
vec![10, 20, 30, 40, 50]
});
// --- Example 2: Using OnceCell for a global configuration string ---
// This OnceCell can be initialized exactly once at runtime.
static CONFIG_MESSAGE: OnceCell<String> = OnceCell::new();
fn main() {
println!("--- Lazy Initialization Example ---");
// Accessing MY_LAZY_VECTOR for the first time triggers its initialization.
println!("First access to MY_LAZY_VECTOR: {:?}", MY_LAZY_VECTOR[0]);
// Subsequent accesses use the already initialized value, no re-initialization occurs.
println!("Second access to MY_LAZY_VECTOR: {:?}", MY_LAZY_VECTOR[1]);
// Demonstrate thread-safe lazy initialization
let handles: Vec<_> = (0..5)
.map(|i| {
thread::spawn(move || {
println!("Thread {} accessing MY_LAZY_VECTOR: {:?}", i, MY_LAZY_VECTOR[i % 5]);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
println!("\n--- OnceCell Initialization Example ---");
// Try to initialize CONFIG_MESSAGE
let init_result = CONFIG_MESSAGE.set("Application started successfully.".to_string());
match init_result {
Ok(_) => println!("CONFIG_MESSAGE initialized."),
Err(val) => println!("Failed to initialize CONFIG_MESSAGE: already set to '{}'.", val),
}
// Get the value from CONFIG_MESSAGE
if let Some(msg) = CONFIG_MESSAGE.get() {
println!("Current CONFIG_MESSAGE: {}", msg);
} else {
println!("CONFIG_MESSAGE is not set.");
}
// Try to initialize again (will fail)
let reinit_result = CONFIG_MESSAGE.set("Another message.".to_string());
match reinit_result {
Ok(_) => println!("CONFIG_MESSAGE re-initialized (this shouldn't happen)."),
Err(val) => println!("CONFIG_MESSAGE re-initialization failed, value was: '{}'. This is expected.", val),
}
// Demonstrate `get_or_init` which is often preferred for simpler cases
static DEFAULT_PORT: OnceCell<u16> = OnceCell::new();
let port = DEFAULT_PORT.get_or_init(|| {
println!("Initializing DEFAULT_PORT with default value...");
// In a real app, this might read from an env var or config file
8080
});
println!("Application running on port: {}", port);
// Subsequent calls to `get_or_init` will not re-initialize
let another_port_access = DEFAULT_PORT.get_or_init(|| {
println!("This message will not appear because DEFAULT_PORT is already initialized.");
9090 // This value will be ignored
});
println!("Another access to port: {}", another_port_access);
}
```








once_cell