Loading page…
Rust walkthroughs
Loading page…
once_cell::unsync::Lazy and once_cell::sync::Lazy for single-threaded contexts?The once_cell crate provides two Lazy types that share the same initialization behavior but differ in thread-safety guarantees: unsync::Lazy for single-threaded contexts and sync::Lazy for multi-threaded scenarios. The unsync variant is simpler and faster because it omits atomic operations and synchronization primitives, while the sync variant uses OnceCell with atomic operations to ensure thread-safe initialization. For single-threaded applications, unsync::Lazy provides the same lazy initialization semantics without the synchronization overhead, making it the appropriate choice when thread safety is unnecessary.
use once_cell::unsync::Lazy;
static CONFIG: Lazy<String> = Lazy::new(|| {
// Called once, first time CONFIG is accessed
println!("Initializing config");
std::fs::read_to_string("config.txt").unwrap_or_default()
});
fn main() {
println!("Before access");
let config = &*CONFIG; // Initialization happens here
println!("Config: {}", config);
let config2 = &*CONFIG; // No re-initialization
}unsync::Lazy initializes the value on first access, storing it for subsequent accesses.
use once_cell::sync::Lazy;
static GLOBAL_CONFIG: Lazy<String> = Lazy::new(|| {
println!("Initializing global config");
std::fs::read_to_string("config.txt").unwrap_or_default()
});
fn main() {
std::thread::spawn(|| {
let config = &*GLOBAL_CONFIG;
});
let config = &*GLOBAL_CONFIG;
}sync::Lazy is safe to access from multiple threads, ensuring initialization happens exactly once.
// unsync::Lazy - simplified implementation
pub struct Lazy<T, F = fn() -> T> {
cell: OnceCell<T>, // No atomic operations
init: Cell<Option<F>>,
}
// sync::Lazy - simplified implementation
pub struct Lazy<T, F = fn() -> T> {
cell: OnceCell<T>, // Uses atomics internally
init: Mutex<Option<F>>, // Thread-safe initialization
}unsync::Lazy uses Cell for the initialization closure; sync::Lazy uses Mutex.
use once_cell::sync::Lazy;
use std::sync::Arc;
// sync::Lazy is required for statics accessed from multiple threads
static SHARED_CACHE: Lazy<Vec<String>> = Lazy::new(|| {
vec!["initialized".to_string()]
});
fn worker_thread() {
let cache = &*SHARED_CACHE; // Thread-safe access
}
fn main() {
let handles: Vec<_> = (0..10)
.map(|_| std::thread::spawn(worker_thread))
.collect();
for handle in handles {
handle.join().unwrap();
}
}sync::Lazy ensures only one thread initializes the value even with concurrent access.
use once_cell::unsync::Lazy;
// unsync::Lazy in single-threaded context
fn single_threaded() {
let config = Lazy::new(|| {
std::fs::read_to_string("config.txt").unwrap_or_default()
});
let value = &*config;
let again = &*config; // Reuses same value
println!("{}", value);
}unsync::Lazy is appropriate when no cross-thread access occurs.
use once_cell::{sync::Lazy, unsync};
fn benchmark_overhead() {
// unsync::Lazy - no atomic operations
let unsync_lazy = unsync::Lazy::new(|| 42);
// sync::Lazy - uses atomic operations for thread safety
let sync_lazy: Lazy<i32> = Lazy::new(|| 42);
// Access overhead comparison:
// unsync::Lazy: Single memory check, no atomics
// sync::Lazy: Atomic check with Release/Acquire ordering
}unsync::Lazy avoids atomic operations that have overhead even on single-threaded access.
use once_cell::unsync::Lazy;
fn mutable_initialization() {
// unsync::Lazy allows interior mutability in init
let mut counter = 0;
let lazy = Lazy::new(|| {
counter += 1; // Can mutate local state
counter * 2
});
// sync::Lazy cannot mutate captured variables
// across thread boundaries safely
}unsync::Lazy can capture mutable local references; sync::Lazy cannot.
use once_cell::unsync::Lazy;
struct ExpensiveInit {
data: Vec<u8>,
}
impl ExpensiveInit {
fn new() -> Self {
// Expensive computation
std::thread::sleep(std::time::Duration::from_millis(100));
Self { data: vec![0; 1024] }
}
}
fn deferred_initialization() {
let lazy = Lazy::new(|| ExpensiveInit::new());
// Initialization deferred until first access
println!("Not initialized yet");
let _ = &*lazy; // Now initialized
// No re-initialization on subsequent access
let _ = &*lazy;
}Both variants guarantee exactly one initialization call.
use once_cell::unsync::Lazy;
use std::fs;
enum InitError {
Io(std::io::Error),
}
fn fallible_init() {
// Lazy cannot return Result - initialization must succeed
let config: Lazy<String> = Lazy::new(|| {
// Panics if file doesn't exist
fs::read_to_string("config.txt").expect("config required")
});
// For fallible initialization, use Lazy<Option<T>> or OnceCell
}Both variants require initialization to succeed; panics propagate to the caller.
use once_cell::unsync::Lazy;
fn function_with_static() {
// Local static using unsync::Lazy
static LOCAL_CACHE: Lazy<Vec<i32>> = Lazy::new(|| {
(0..100).collect()
});
let cache = &*LOCAL_CACHE;
}
fn main() {
function_with_static();
function_with_static(); // Reuses same cache
}unsync::Lazy can be used for local statics that are only accessed from the main thread.
use once_cell::unsync::Lazy;
use std::rc::Rc;
fn non_send_types() {
// unsync::Lazy can hold non-Send types
let lazy: Lazy<Rc<i32>> = Lazy::new(|| {
Rc::new(42)
});
let rc = &*lazy;
let rc2 = Rc::clone(rc);
// sync::Lazy cannot hold non-Send types
// because it must be accessible from any thread
}unsync::Lazy can store types that don't implement Send.
use once_cell::unsync::Lazy;
fn capture_mutable() {
let mut state = 0;
let lazy = Lazy::new(|| {
state += 1; // Mutates captured variable
state
});
// sync::Lazy cannot do this because
// captured values must be Send
}unsync::Lazy allows capturing mutable references to local variables.
use once_cell::{sync::Lazy, unsync::Lazy};
// Use sync::Lazy for:
// - Statics accessed from multiple threads
// - Types that implement Send
// - When thread safety is required
static GLOBAL_STATE: Lazy<Vec<String>> = Lazy::new(|| vec![]);
static CONFIG: Lazy<Config> = Lazy::new(|| Config::load());
// Use unsync::Lazy for:
// - Single-threaded contexts
// - Local variables that don't escape the thread
// - Types that don't implement Send
fn local_context() {
let local_cache: unsync::Lazy<Vec<i32>> = Lazy::new(|| {
(0..100).collect()
});
let config: unsync::Lazy<Config> = Lazy::new(|| {
Config::from_file("local.json")
});
}Choose sync::Lazy when thread safety matters; unsync::Lazy for single-threaded contexts.
use once_cell::{sync::Lazy, unsync::Lazy};
fn conversion_example() {
// Cannot directly convert between sync and unsync
// If you need both, create separately:
let unsync: unsync::Lazy<String> = unsync::Lazy::new(|| "local".to_string());
// For thread-safe context:
let sync: Lazy<String> = Lazy::new(|| "global".to_string());
// Or restructure to use OnceCell directly
use once_cell::sync::OnceCell;
let cell: OnceCell<String> = OnceCell::new();
}No direct conversion exists; choose the appropriate variant from the start.
use once_cell::unsync::Lazy;
// Pattern: Lazy-loaded configuration
struct AppConfig {
database_url: String,
api_key: String,
}
impl AppConfig {
fn load() -> Self {
Self {
database_url: std::env::var("DATABASE_URL").unwrap_or_default(),
api_key: std::env::var("API_KEY").unwrap_or_default(),
}
}
}
// Pattern: Cached computation
fn cached_computation() {
let expensive_result: Lazy<Vec<u8>> = Lazy::new(|| {
(0..1000).map(|x| (x % 256) as u8).collect()
});
// Only computed once even if function called multiple times
let result = &*expensive_result;
}
// Pattern: Lazy regex compilation
use regex::Regex;
fn lazy_regex() {
static PATTERN: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"\d+").unwrap()
});
let text = "abc123def";
if PATTERN.is_match(text) {
println!("Found digits");
}
}Common patterns apply to both variants, differing only in thread safety.
use once_cell::unsync::Lazy;
use std::collections::HashMap;
struct Config {
settings: HashMap<String, String>,
}
impl Config {
fn load() -> Self {
let mut settings = HashMap::new();
settings.insert("host".to_string(), "localhost".to_string());
settings.insert("port".to_string(), "8080".to_string());
Self { settings }
}
}
fn main() {
// Single-threaded application uses unsync::Lazy
let config: Lazy<Config> = Lazy::new(Config::load);
let host = config.settings.get("host").unwrap();
println!("Host: {}", host);
// Subsequent access is fast
let port = config.settings.get("port").unwrap();
println!("Port: {}", port);
}For CLI tools and single-threaded apps, unsync::Lazy avoids unnecessary synchronization.
use once_cell::sync::Lazy;
use std::sync::Arc;
struct DatabasePool {
connections: Vec<String>,
}
impl DatabasePool {
fn new() -> Self {
Self {
connections: vec!["conn1".to_string(), "conn2".to_string()],
}
}
}
static DB_POOL: Lazy<Arc<DatabasePool>> = Lazy::new(|| {
Arc::new(DatabasePool::new())
});
fn handle_request() {
// Multiple threads can access safely
let pool = Arc::clone(&*DB_POOL);
// ... use pool
}
fn main() {
// Server spawns multiple threads
for _ in 0..10 {
std::thread::spawn(|| {
handle_request();
});
}
}For servers and multi-threaded applications, sync::Lazy ensures safe concurrent access.
Key differences:
| Aspect | unsync::Lazy | sync::Lazy |
|--------|----------------|--------------|
| Thread safety | Not thread-safe | Thread-safe |
| Initialization closure | Cell<Option<F>> | Mutex<Option<F>> |
| Atomic operations | None | Uses atomics |
| Send bound on T | Not required | Required |
| Performance | Faster access | Slower due to atomics |
| Use case | Single-threaded | Multi-threaded |
When to use:
| Context | Choice |
|---------|--------|
| Global static in multi-threaded app | sync::Lazy |
| Local variable in function | unsync::Lazy |
| Types not implementing Send | unsync::Lazy |
| CLI tool, single-threaded | unsync::Lazy |
| Web server, multi-threaded | sync::Lazy |
Key insight: The unsync::Lazy and sync::Lazy types provide identical lazy initialization semantics but differ in thread-safety mechanisms. unsync::Lazy uses Cell for interior mutability without atomic operations, making it faster for single-threaded contexts. sync::Lazy uses atomic operations and Mutex to ensure exactly-once initialization across concurrent access, incurring synchronization overhead. The unsync variant can store non-Send types like Rc and capture mutable references to local variables, while sync::Lazy requires Send types. For single-threaded applications like CLI tools or when the lazy value is confined to one thread, unsync::Lazy provides the same lazy initialization without unnecessary synchronization costs.