Loading page…
Rust walkthroughs
Loading page…
The once_cell crate provides two types for lazy initialization: Lazy<T> for values that are initialized on first access, and OnceCell<T> for values that may or may not be set. These types are thread-safe and eliminate the need for messy static mut patterns or manual synchronization. They're perfect for global configuration, singleton patterns, cached computations, and any scenario where you need "initialize once, access many times" semantics.
Key concepts:
sync module provides thread-safe versionslazy_static!# Cargo.toml
[dependencies]
once_cell = "1.0"use once_cell::sync::Lazy;
// Global configuration, initialized on first access
static CONFIG: Lazy<Vec<String>> = Lazy::new(|| {
println!("Initializing config...");
vec!["config1".to_string(), "config2".to_string()]
});
fn main() {
println!("Before accessing CONFIG");
println!("Config: {:?}", *CONFIG);
println!("After accessing CONFIG");
// Second access doesn't reinitialize
println!("Config again: {:?}", *CONFIG);
}use once_cell::sync::Lazy;
use std::collections::HashMap;
// Simple lazy static
static NUMBER: Lazy<i32> = Lazy::new(|| {
println!("Computing NUMBER...");
42
});
// Lazy static with complex initialization
static WORD_COUNTS: Lazy<HashMap<&'static str, usize>> = Lazy::new(|| {
let text = "the quick brown fox jumps over the lazy dog";
let mut counts = HashMap::new();
for word in text.split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
counts
});
// Lazy static that requires computation
static PRIMES: Lazy<Vec<u64>> = Lazy::new(|| {
(2..100).filter(|&n| is_prime(n)).collect()
});
fn is_prime(n: u64) -> bool {
if n < 2 { return false; }
(2..=(n as f64).sqrt() as u64).all(|i| n % i != 0)
}
fn main() {
println!("Starting...");
// NUMBER not initialized yet
println!("About to access NUMBER");
// Initialize on first access
println!("NUMBER = {}", *NUMBER);
// Already initialized, won't print "Computing..." again
println!("NUMBER again = {}", *NUMBER);
// Access lazy HashMap
println!("\nWord counts: {:?}", *WORD_COUNTS);
// Access lazy Vec
println!("\nPrimes under 100: {:?}", *PRIMES);
}use once_cell::sync::OnceCell;
static INSTANCE: OnceCell<String> = OnceCell::new();
fn main() {
// Check if set
println!("Is set: {}", INSTANCE.get().is_some());
// Set the value (can only be done once)
let result = INSTANCE.set("Hello, World!".to_string());
println!("Set result: {:?}", result); // Ok(())
// Try to set again
let result = INSTANCE.set("Goodbye".to_string());
println!("Set again result: {:?}", result); // Err(value)
// Get the value
if let Some(value) = INSTANCE.get() {
println!("Value: {}", value);
}
// get_or_init initializes if not set
static ANOTHER: OnceCell<Vec<i32>> = OnceCell::new();
let value = ANOTHER.get_or_init(|| vec![1, 2, 3]);
println!("Another: {:?}", value);
}use once_cell::sync::OnceCell;
fn main() {
let cell: OnceCell<String> = OnceCell::new();
// Check if initialized
println!("Initialized: {}", cell.get().is_some());
// Set value
cell.set("first".to_string()).unwrap();
println!("After set: {:?}", cell.get());
// Try to set again (fails)
match cell.set("second".to_string()) {
Ok(()) => println!("Set succeeded"),
Err(value) => println!("Set failed, value was: {}", value),
}
// Get or initialize
let cell2: OnceCell<i32> = OnceCell::new();
let value = cell2.get_or_init(|| {
println!("Computing value...");
42
});
println!("Value: {}", value);
// Get or try to initialize (may fail)
let cell3: OnceCell<i32> = OnceCell::new();
let result = cell3.get_or_try_init(|| -> Result<i32, &'static str> {
Ok(100)
});
println!("Result: {:?}", result);
}use once_cell::{sync::Lazy, unsync};
// sync::Lazy - thread-safe, can be used in statics
static GLOBAL: Lazy<i32> = Lazy::new(|| {
println!("Initializing GLOBAL");
42
});
// unsync::Lazy - not thread-safe, but slightly faster
// Cannot be used in statics, only in local variables
fn local_lazy() {
let local: unsync::Lazy<i32> = unsync::Lazy::new(|| {
println!("Initializing local");
100
});
println!("Local: {}", *local);
}
fn main() {
// Thread-safe version
println!("Global: {}", *GLOBAL);
// Unsynchronized version
local_lazy();
// OnceCell comparison
let sync_cell: once_cell::sync::OnceCell<i32> = once_cell::sync::OnceCell::new();
let unsync_cell: once_cell::unsync::OnceCell<i32> = once_cell::unsync::OnceCell::new();
sync_cell.set(1).unwrap();
unsync_cell.set(2).unwrap();
println!("Sync cell: {:?}", sync_cell.get());
println!("Unsync cell: {:?}", unsync_cell.get());
}use once_cell::sync::{Lazy, OnceCell};
use std::env;
#[derive(Debug)]
struct Config {
database_url: String,
api_key: String,
max_connections: usize,
debug: bool,
}
static CONFIG: OnceCell<Config> = OnceCell::new();
fn get_config() -> &'static Config {
CONFIG.get_or_init(|| {
Config {
database_url: env::var("DATABASE_URL")
.unwrap_or_else(|_| "localhost:5432".to_string()),
api_key: env::var("API_KEY")
.unwrap_or_else(|_| "default-key".to_string()),
max_connections: env::var("MAX_CONNECTIONS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(10),
debug: env::var("DEBUG").is_ok(),
}
})
}
fn main() {
// First call initializes the config
let config = get_config();
println!("Config: {:?}", config);
// Subsequent calls return the same instance
let config2 = get_config();
assert!(std::ptr::eq(config, config2));
println!("Same instance: {}", std::ptr::eq(config, config2));
}use once_cell::sync::Lazy;
use std::collections::HashMap;
struct Cache {
data: HashMap<String, String>,
}
impl Cache {
fn new() -> Self {
println!("Creating cache singleton...");
let mut data = HashMap::new();
data.insert("key1".to_string(), "value1".to_string());
Self { data }
}
fn get(&self, key: &str) -> Option<&String> {
self.data.get(key)
}
fn insert(&mut self, key: String, value: String) {
self.data.insert(key, value);
}
}
// Global singleton instance
static CACHE: Lazy<std::sync::Mutex<Cache>> = Lazy::new(|| {
std::sync::Mutex::new(Cache::new())
});
fn main() {
// Access the singleton
{
let cache = CACHE.lock().unwrap();
println!("key1: {:?}", cache.get("key1"));
}
// Modify the singleton
{
let mut cache = CACHE.lock().unwrap();
cache.insert("key2".to_string(), "value2".to_string());
}
// Access again
{
let cache = CACHE.lock().unwrap();
println!("key2: {:?}", cache.get("key2"));
}
}use once_cell::sync::Lazy;
use regex::Regex;
// Compile regex once, use many times
static EMAIL_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap()
});
static PHONE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^\d{3}-\d{3}-\d{4}$").unwrap()
});
fn is_valid_email(email: &str) -> bool {
EMAIL_REGEX.is_match(email)
}
fn is_valid_phone(phone: &str) -> bool {
PHONE_REGEX.is_match(phone)
}
fn main() {
let emails = vec!["test@example.com", "invalid-email", "user@domain.org"];
for email in emails {
println!("{}: valid email = {}", email, is_valid_email(email));
}
let phones = vec!["123-456-7890", "1234567890", "123-45-6789"];
for phone in phones {
println!("{}: valid phone = {}", phone, is_valid_phone(phone));
}
}use once_cell::sync::{Lazy, OnceCell};
use std::time::Instant;
// Fibonacci sequence cache
static FIB_CACHE: Lazy<std::sync::Mutex<Vec<u64>>> = Lazy::new(|| {
std::sync::Mutex::new(vec![0, 1])
});
fn fibonacci(n: usize) -> u64 {
let mut cache = FIB_CACHE.lock().unwrap();
while cache.len() <= n {
let next = cache[cache.len() - 1] + cache[cache.len() - 2];
cache.push(next);
}
cache[n]
}
// Heavy computation result
static HEAVY_RESULT: OnceCell<String> = OnceCell::new();
fn get_heavy_result() -> &'static str {
HEAVY_RESULT.get_or_init(|| {
println!("Performing heavy computation...");
std::thread::sleep(std::time::Duration::from_millis(500));
"Computed result".to_string()
})
}
fn main() {
// First call computes
let start = Instant::now();
let result = get_heavy_result();
println!("First call: {} ({:?})", result, start.elapsed());
// Second call uses cached value
let start = Instant::now();
let result = get_heavy_result();
println!("Second call: {} ({:?})", result, start.elapsed());
// Fibonacci caching
println!("\nFibonacci:");
for n in [10, 20, 30, 10, 20] {
println!(" fib({}) = {}", n, fibonacci(n));
}
}use once_cell::sync::Lazy;
use std::sync::Mutex;
// Simulated connection
#[derive(Debug)]
struct Connection {
id: usize,
}
impl Connection {
fn new(id: usize) -> Self {
println!("Creating connection {}", id);
Self { id }
}
fn query(&self, sql: &str) -> String {
format!("[Conn {}] Result of: {}", self.id, sql)
}
}
struct ConnectionPool {
connections: Vec<Connection>,
next_id: usize,
}
impl ConnectionPool {
fn new(size: usize) -> Self {
println!("Creating connection pool with {} connections", size);
let connections = (0..size).map(|i| Connection::new(i)).collect();
Self { connections, next_id: 0 }
}
fn get(&mut self) -> &Connection {
let conn = &self.connections[self.next_id % self.connections.len()];
self.next_id += 1;
conn
}
}
// Global connection pool
static POOL: Lazy<Mutex<ConnectionPool>> = Lazy::new(|| {
Mutex::new(ConnectionPool::new(5))
});
fn query(sql: &str) -> String {
let mut pool = POOL.lock().unwrap();
pool.get().query(sql)
}
fn main() {
println!("Starting application...");
// Pool created on first query
let result = query("SELECT * FROM users");
println!("Result: {}", result);
// Reuse existing pool
let result = query("SELECT * FROM orders");
println!("Result: {}", result);
}use once_cell::sync::Lazy;
use std::env;
struct EnvConfig {
host: String,
port: u16,
debug: bool,
}
static ENV: Lazy<EnvConfig> = Lazy::new(|| {
EnvConfig {
host: env::var("HOST").unwrap_or_else(|_| "localhost".to_string()),
port: env::var("PORT")
.ok()
.and_then(|p| p.parse().ok())
.unwrap_or(8080),
debug: env::var("DEBUG").is_ok(),
}
});
fn main() {
// Environment parsed once
println!("Host: {}", ENV.host);
println!("Port: {}", ENV.port);
println!("Debug: {}", ENV.debug);
// Same values on subsequent access
println!("\nConfig is consistent:");
println!("Host again: {}", ENV.host);
}use once_cell::sync::Lazy;
use std::cell::RefCell;
thread_local! {
static COUNTER: RefCell<i32> = RefCell::new(0);
}
// Thread-local lazy initialization
static THREAD_DATA: Lazy<std::thread::LocalKey<RefCell<Vec<String>>>> = Lazy::new(|| {
std::thread_local! {
static DATA: RefCell<Vec<String>> = RefCell::new(Vec::new());
}
DATA
});
fn increment_counter() -> i32 {
COUNTER.with(|c| {
let mut counter = c.borrow_mut();
*counter += 1;
*counter
})
}
fn main() {
println!("Main thread counter:");
for _ in 0..3 {
println!(" {}", increment_counter());
}
// Spawn a new thread - has its own counter
let handle = std::thread::spawn(|| {
println!("\nChild thread counter:");
for _ in 0..3 {
println!(" {}", increment_counter());
}
});
handle.join().unwrap();
println!("\nMain thread counter again:");
println!(" {}", increment_counter());
}use once_cell::sync::OnceCell;
static STATUS: OnceCell<String> = OnceCell::new();
fn main() {
// Check if initialized
println!("Initialized: {}", STATUS.get().is_some());
// Get current status (None if not set)
println!("Current: {:?}", STATUS.get());
// Set the value
STATUS.set("ready".to_string()).unwrap();
// Now it's initialized
println!("Initialized: {}", STATUS.get().is_some());
println!("Current: {:?}", STATUS.get());
// get_or_init with condition
static OPTIONAL: OnceCell<i32> = OnceCell::new();
if let Some(value) = OPTIONAL.get() {
println!("Optional already set: {}", value);
} else {
println!("Optional not set yet");
OPTIONAL.set(42).ok();
}
}// Requires: once_cell = { version = "1.0", features = ["serde"] }
use once_cell::sync::Lazy;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Settings {
name: String,
version: String,
features: Vec<String>,
}
static SETTINGS: Lazy<Settings> = Lazy::new(|| {
let json = r#"
{
"name": "my-app",
"version": "1.0.0",
"features": ["auth", "logging", "cache"]
}
"#;
serde_json::from_str(json).expect("Failed to parse settings")
});
fn main() {
println!("App: {} v{}", SETTINGS.name, SETTINGS.version);
println!("Features: {:?}", SETTINGS.features);
}use once_cell::sync::{Lazy, OnceCell};
// Lazy: automatically initialized on first access
static AUTO: Lazy<String> = Lazy::new(|| {
println!("Lazy initializing AUTO");
"auto-initialized".to_string()
});
// OnceCell: explicitly set once
static MANUAL: OnceCell<String> = OnceCell::new();
fn main() {
println!("=== Lazy ===");
println!("Before access");
println!("Value: {}", *AUTO); // Initializes here
println!("After access");
println!("\n=== OnceCell ===");
println!("Before set: {:?}", MANUAL.get());
MANUAL.set("manually-set".to_string()).unwrap();
println!("After set: {:?}", MANUAL.get());
// Use Lazy when:
// - You always need the value
// - You want automatic initialization
// - The initialization is deterministic
// Use OnceCell when:
// - The value may never be needed
// - You need explicit control over when it's set
// - Initialization may fail or be conditional
}use once_cell::sync::OnceCell;
use std::fs;
static FILE_CONTENTS: OnceCell<String> = OnceCell::new();
fn load_file() -> Result<&'static str, std::io::Error> {
FILE_CONTENTS.get_or_try_init(|| {
// Simulated file read
println!("Loading file...");
// In real code: fs::read_to_string("config.txt")
Ok("file contents here".to_string())
}).map(|s| s.as_str())
}
fn main() {
// First call loads the file
match load_file() {
Ok(contents) => println!("Contents: {}", contents),
Err(e) => println!("Error: {}", e),
}
// Subsequent calls return cached value
match load_file() {
Ok(contents) => println!("Cached: {}", contents),
Err(e) => println!("Error: {}", e),
}
}use once_cell::sync::{Lazy, OnceCell};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
struct AppState {
users: HashMap<u64, String>,
sessions: HashMap<String, u64>,
config: Config,
}
#[derive(Debug, Clone)]
struct Config {
app_name: String,
version: String,
}
static STATE: Lazy<Arc<Mutex<AppState>>> = Lazy::new(|| {
println!("Initializing application state...");
Arc::new(Mutex::new(AppState {
users: HashMap::new(),
sessions: HashMap::new(),
config: Config {
app_name: "MyApp".to_string(),
version: "1.0.0".to_string(),
},
}))
});
static SHUTDOWN_FLAG: OnceCell<bool> = OnceCell::new();
fn register_user(name: String) -> u64 {
let mut state = STATE.lock().unwrap();
let id = state.users.len() as u64 + 1;
state.users.insert(id, name);
id
}
fn get_user(id: u64) -> Option<String> {
let state = STATE.lock().unwrap();
state.users.get(&id).cloned()
}
fn is_shutting_down() -> bool {
SHUTDOWN_FLAG.get().copied().unwrap_or(false)
}
fn initiate_shutdown() {
SHUTDOWN_FLAG.set(true).ok();
}
fn main() {
// State initialized on first access
let id1 = register_user("Alice".to_string());
let id2 = register_user("Bob".to_string());
println!("Registered users:");
println!(" {}: {:?}", id1, get_user(id1));
println!(" {}: {:?}", id2, get_user(id2));
// Check shutdown status
println!("\nShutting down: {}", is_shutting_down());
// Initiate shutdown
initiate_shutdown();
println!("Shutting down: {}", is_shutting_down());
}// OLD: using lazy_static!
// lazy_static::lazy_static! {
// static ref CONFIG: Vec<String> = {
// vec!["value1".to_string(), "value2".to_string()]
// };
// }
// NEW: using once_cell
use once_cell::sync::Lazy;
static CONFIG: Lazy<Vec<String>> = Lazy::new(|| {
vec!["value1".to_string(), "value2".to_string()]
});
fn main() {
println!("Config: {:?}", *CONFIG);
// Benefits of once_cell over lazy_static:
// 1. No macro needed - standard Rust syntax
// 2. Better IDE support and documentation
// 3. Works with generic types
// 4. More control with OnceCell
}Lazy<T> automatically initializes on first access — use for values that are always neededOnceCell<T> requires explicit set() — use for values that may or may not be setsync module provides thread-safe versions for use in static itemsunsync module provides faster non-thread-safe versions for local useget_or_init() returns existing value or initializes with closureget_or_try_init() supports fallible initialization with Resultget() returns Option<&T> to check initialization statusMutex for mutable global statelazy_static! macro — no macros neededstd::sync::OnceLock and std::sync::LazyLock are available in Rust 1.70+ as standard library alternatives