Loading page…
Rust walkthroughs
Loading page…
Once Cell provides cell types for lazy initialization and one-time assignment. Unlike std::cell::OnceCell (available since Rust 1.70), the once_cell crate offers additional features like thread-safe lazy static initialization without macros.
Key concepts:
When to use Once Cell:
When NOT to use Once Cell:
use once_cell::unsync::OnceCell;
fn main() {
// Single-threaded once cell
let cell = OnceCell::new();
// Get returns None if not set
assert!(cell.get().is_none());
// Set the value
cell.set("Hello".to_string()).unwrap();
// Get returns Some after set
assert_eq!(cell.get(), Some(&"Hello".to_string()));
// Cannot set again
assert!(cell.set("World".to_string()).is_err());
}use once_cell::unsync::Lazy;
fn main() {
// Lazy initialization - closure runs on first access
let expensive = Lazy::new(|| {
println!("Computing...");
42
});
println!("Before access");
println!("Value: {}", *expensive); // "Computing" printed here
println!("Value: {}", *expensive); // No recomputation
}use once_cell::sync::OnceCell;
use std::thread;
static GLOBAL: OnceCell<String> = OnceCell::new();
fn main() {
let handles: Vec<_> = (0..5)
.map(|i| {
thread::spawn(move || {
// All threads see the same value
GLOBAL.get_or_init(|| format!("Thread {} was first", i))
})
})
.collect();
for handle in handles {
println!("{}", handle.join().unwrap());
}
// All threads report the same value
println!("Final: {:?}", GLOBAL.get());
}use once_cell::sync::Lazy;
use std::collections::HashMap;
// Global configuration loaded once
static CONFIG: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
let mut m = HashMap::new();
m.insert("host", "localhost");
m.insert("port", "8080");
m.insert("debug", "true");
m
});
fn main() {
// First access initializes
println!("Host: {}", CONFIG.get("host").unwrap());
// Subsequent accesses reuse the same value
println!("Port: {}", CONFIG.get("port").unwrap());
}use once_cell::sync::Lazy;
struct Config {
name: String,
version: String,
}
impl Config {
fn load() -> Self {
println!("Loading config...");
Config {
name: "MyApp".to_string(),
version: "1.0.0".to_string(),
}
}
}
static APP_CONFIG: Lazy<Config> = Lazy::new(Config::load);
fn get_config() -> &'static Config {
&APP_CONFIG
}
fn main() {
println!("Starting app");
let config = get_config();
println!("Name: {}", config.name);
}use once_cell::sync::Lazy;
use std::sync::Arc;
// Simulated connection pool
struct DbPool {
connections: Vec<String>,
}
impl DbPool {
fn new() -> Self {
println!("Creating pool...");
DbPool {
connections: vec!["conn1".to_string(), "conn2".to_string()],
}
}
fn get(&self) -> Option<&String> {
self.connections.first()
}
}
static DB_POOL: Lazy<Arc<DbPool>> = Lazy::new(|| {
Arc::new(DbPool::new())
});
fn main() {
// Pool created on first use
let conn = DB_POOL.get().unwrap();
println!("Got connection: {}", conn);
}use once_cell::sync::OnceCell;
static CACHE: OnceCell<Vec<String>> = OnceCell::new();
fn get_or_compute(items: &[&str]) -> &'static Vec<String> {
CACHE.get_or_init(|| {
items.iter().map(|s| s.to_uppercase()).collect()
})
}
fn main() {
let result = get_or_compute(&["hello", "world"]);
println!("{:?}", result);
// Subsequent calls return same cached value
let result2 = get_or_compute(&["different", "args"]);
println!("{:?}", result2); // Same as before
}use once_cell::sync::Lazy;
use std::sync::Mutex;
static COUNTER: Lazy<Mutex<i32>> = Lazy::new(|| Mutex::new(0));
fn increment() -> i32 {
let mut guard = COUNTER.lock().unwrap();
*guard += 1;
*guard
}
fn main() {
println!("{}", increment()); // 1
println!("{}", increment()); // 2
println!("{}", increment()); // 3
}use once_cell::sync::Lazy;
use regex::Regex;
// Compile regex once
static HEX_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^[0-9a-fA-F]+$").unwrap()
});
static EMAIL_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap()
});
fn is_hex(s: &str) -> bool {
HEX_REGEX.is_match(s)
}
fn is_valid_email(s: &str) -> bool {
EMAIL_REGEX.is_match(s)
}
fn main() {
println!("Is hex: {}", is_hex("deadbeef"));
println!("Is email: {}", is_valid_email("test@example.com"));
}use once_cell::sync::OnceCell;
use std::fs;
static FILE_CONTENTS: OnceCell<Result<String, std::io::Error>> = OnceCell::new();
fn get_file_contents() -> &'static Result<String, std::io::Error> {
FILE_CONTENTS.get_or_init(|| {
fs::read_to_string("config.txt")
})
}
fn main() {
match get_file_contents() {
Ok(contents) => println!("Contents: {}", contents),
Err(e) => eprintln!("Error: {}", e),
}
}use once_cell::sync::OnceCell;
use std::sync::OnceLock; // Standard library equivalent (Rust 1.70+)
// once_cell version
static ONCE_CELL: OnceCell<i32> = OnceCell::new();
// std version
static ONCE_LOCK: OnceLock<i32> = OnceLock::new();
fn main() {
ONCE_CELL.set(42).ok();
ONCE_LOCK.set(42).ok();
println!("once_cell: {:?}", ONCE_CELL.get());
println!("std: {:?}", ONCE_LOCK.get());
}use once_cell::unsync::{OnceCell, Lazy}; // Single-threaded
use once_cell::sync::{OnceCell as SyncOnceCell, Lazy as SyncLazy}; // Thread-safe
fn main() {
// Unsync - single-threaded only
let unsync_cell: OnceCell<i32> = OnceCell::new();
let unsync_lazy: Lazy<i32> = Lazy::new(|| 42);
// Sync - thread-safe
let sync_cell: SyncOnceCell<i32> = SyncOnceCell::new();
let sync_lazy: SyncLazy<i32> = SyncLazy::new(|| 42);
println!("Unsync: {:?}", unsync_cell.get());
println!("Sync: {:?}", sync_cell.get());
}use once_cell::sync::Lazy;
use std::collections::HashMap;
struct Cache {
data: HashMap<String, String>,
}
impl Cache {
fn new() -> Self {
println!("Creating cache...");
Cache {
data: HashMap::new(),
}
}
}
static CACHE: Lazy<Cache> = Lazy::new(Cache::new);
static API_ENDPOINTS: Lazy<Vec<&'static str>> = Lazy::new(|| {
vec!["/api/users", "/api/posts", "/api/comments"]
});
fn main() {
println!("Endpoints: {:?}", *API_ENDPOINTS);
}use once_cell::unsync::OnceCell;
struct Parser {
source: String,
parsed: OnceCell<Vec<String>>,
}
impl Parser {
fn new(source: String) -> Self {
Parser {
source,
parsed: OnceCell::new(),
}
}
fn get_parsed(&self) -> &Vec<String> {
self.parsed.get_or_init(|| {
self.source.split_whitespace()
.map(|s| s.to_string())
.collect()
})
}
}
fn main() {
let parser = Parser::new("Hello World Rust".to_string());
// Parsing happens on first call
let parsed = parser.get_parsed();
println!("{:?}", parsed);
// Second call returns cached result
let parsed2 = parser.get_parsed();
println!("{:?}", parsed2);
}use once_cell::sync::OnceCell;
static MAYBE_VALUE: OnceCell<String> = OnceCell::new();
fn main() {
// try_get returns Option
match MAYBE_VALUE.get() {
Some(v) => println!("Already set: {}", v),
None => println!("Not set yet"),
}
// get_mut returns Option for modification
// Only works with unsync OnceCell
}use once_cell::sync::Lazy;
static FIRST: Lazy<i32> = Lazy::new(|| {
println!("Initializing FIRST");
1
});
static SECOND: Lazy<i32> = Lazy::new(|| {
println!("Initializing SECOND");
*FIRST + 1
});
fn main() {
println!("Starting");
println!("Second: {}", *SECOND); // Initializes SECOND, which initializes FIRST
println!("First: {}", *FIRST); // Already initialized
}use once_cell::sync::OnceCell;
use std::thread;
use std::sync::Arc;
fn main() {
let cell = Arc::new(OnceCell::new());
let handles: Vec<_> = (0..3)
.map(|i| {
let cell = Arc::clone(&cell);
thread::spawn(move || {
let value = cell.get_or_init(|| {
println!("Thread {} initializing", i);
thread::sleep(std::time::Duration::from_millis(100));
format!("Initialized by {}", i)
});
println!("Thread {} got: {}", i, value);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}use once_cell::sync::Lazy;
use std::sync::Arc;
use std::collections::HashSet;
static BLOCKLIST: Lazy<Arc<HashSet<&'static str>>> = Lazy::new(|| {
let mut set = HashSet::new();
set.insert("spam.com");
set.insert("malware.net");
Arc::new(set)
});
fn is_blocked(domain: &str) -> bool {
BLOCKLIST.contains(domain)
}
fn main() {
println!("Blocked: {}", is_blocked("spam.com"));
println!("Allowed: {}", is_blocked("example.com"));
}use once_cell::unsync::OnceCell;
fn main() {
let mut cell = OnceCell::new();
cell.set(42).unwrap();
// OnceCell doesn't support clearing
// To "reset", create a new cell:
let cell = OnceCell::new();
cell.set(100).unwrap();
// Or use Option<T> if you need mutability
let mut flexible: Option<i32> = None;
flexible = Some(42);
flexible = Some(100); // Can change
}Once Cell Key Imports:
// Single-threaded
use once_cell::unsync::{OnceCell, Lazy};
// Thread-safe
use once_cell::sync::{OnceCell, Lazy};Main Types:
| Type | Thread Safety | Use Case |
|------|---------------|----------|
| unsync::OnceCell | No | One-time assignment |
| unsync::Lazy | No | Lazy initialization |
| sync::OnceCell | Yes | Thread-safe one-time assignment |
| sync::Lazy | Yes | Thread-safe lazy static |
Key Methods:
// OnceCell
cell.get(); // Option<&T>
cell.set(value); // Result<(), T>
cell.get_or_init(|| value); // &T
// Lazy
Lazy::new(|| value); // Create lazy
&*lazy_value; // Access and initializeComparison with Alternatives:
| Feature | once_cell | lazy_static | std (1.70+) | |---------|-----------|-------------|-------------| | No macro needed | ✅ | ❌ | ✅ | | Thread-safe | ✅ | ✅ | ✅ | | Nested in function | ✅ | ❌ | ✅ | | Sync types | ✅ | ✅ | ✅ (OnceLock) | | Async init | ❌ | ❌ | ✅ (OnceLock) |
Key Points:
sync::Lazy for global staticssync::OnceCell for optional globalsunsync types for single-threaded codestd::sync::OnceLock if Rust 1.70+