Loading page…
Rust walkthroughs
Loading page…
The once_cell crate provides types for single-assignment cells and lazy static initialization. Unlike lazy_static!, once_cell provides a more ergonomic API using types like Lazy<T> and OnceCell<T>. These types ensure a value is initialized exactly once and can be accessed efficiently afterward. Once_cell is ideal for global static variables, configuration caching, thread-safe lazy initialization, and any scenario where you need to defer expensive initialization until first use.
Key concepts:
sync) and single-threaded (unsync) variants# Cargo.toml
[dependencies]
once_cell = "1.0"use once_cell::sync::Lazy;
static CONFIG: Lazy<Vec<String>> = Lazy::new(|| {
vec![
"setting1".to_string(),
"setting2".to_string(),
]
});
fn main() {
println!("Config: {:?}", *CONFIG);
}use once_cell::sync::Lazy;
// Global static with lazy initialization
static NUMBERS: Lazy<Vec<i32>> = Lazy::new(|| {
println!("Initializing NUMBERS...");
(1..=5).collect()
});
fn main() {
println!("Before first access");
// First access triggers initialization
println!("First: {:?}", *NUMBERS);
// Subsequent accesses reuse the cached value
println!("Second: {:?}", *NUMBERS);
println!("Third: {:?}", *NUMBERS);
// "Initializing NUMBERS..." only prints once
}use once_cell::sync::Lazy;
// once_cell style (recommended)
static NAME: Lazy<String> = Lazy::new(|| "Alice".to_string());
// lazy_static! style (older approach)
// lazy_static! {
// static ref NAME: String = "Alice".to_string();
// }
fn main() {
// once_cell allows dereferencing naturally
println!("Name: {}", *NAME);
println!("Length: {}", NAME.len());
// Can use in type positions easily
let names: [&Lazy<String>; 1] = [&NAME];
for n in names {
println!("Name: {}", **n);
}
}use once_cell::sync::Lazy;
use std::sync::Mutex;
static COUNTER: Lazy<Mutex<i32>> = Lazy::new(|| Mutex::new(0));
fn main() {
// Access and modify the counter
{
let mut num = COUNTER.lock().unwrap();
*num += 1;
println!("Counter: {}", *num);
}
{
let mut num = COUNTER.lock().unwrap();
*num += 1;
println!("Counter: {}", *num);
}
}use once_cell::sync::OnceCell;
static INSTANCE: OnceCell<String> = OnceCell::new();
fn main() {
// Check if initialized
println!("Is initialized: {}", INSTANCE.get().is_some());
// Initialize once
INSTANCE.set("Hello".to_string()).ok();
// Already initialized, set returns Err
let result = INSTANCE.set("World".to_string());
println!("Second set result: {:?}", result); // Err("World")
// Get the value
println!("Value: {:?}", INSTANCE.get());
// Get or initialize
let value = INSTANCE.get_or_init(|| {
println!("This won't run - already initialized");
"New Value".to_string()
});
println!("Value: {}", value);
}use once_cell::unsync::OnceCell;
struct Config {
name: String,
// Expensive to compute, computed lazily
derived: OnceCell<String>,
}
impl Config {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
derived: OnceCell::new(),
}
}
fn get_derived(&self) -> &str {
self.derived.get_or_init(|| {
println!("Computing derived value...");
format!("derived_{}", self.name)
})
}
}
fn main() {
let config = Config::new("test");
println!("Before derived");
println!("Derived: {}", config.get_derived());
println!("Derived: {}", config.get_derived()); // Uses cached value
}use once_cell::sync::Lazy;
use std::collections::HashMap;
#[derive(Debug)]
struct AppConfig {
database_url: String,
api_key: String,
max_connections: usize,
}
impl AppConfig {
fn from_env() -> Self {
// Simulate reading from environment
println!("Loading configuration...");
Self {
database_url: "postgresql://localhost/db".to_string(),
api_key: "secret-key".to_string(),
max_connections: 10,
}
}
}
static CONFIG: Lazy<AppConfig> = Lazy::new(AppConfig::from_env);
fn get_database_url() -> &'static str {
&CONFIG.database_url
}
fn get_api_key() -> &'static str {
&CONFIG.api_key
}
fn main() {
println!("App starting...");
// Config is loaded on first access
println!("DB: {}", get_database_url());
println!("API Key: {}", get_api_key());
// Subsequent calls use the cached config
println!("Max connections: {}", CONFIG.max_connections);
}use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashMap;
static REGEX_CACHE: Lazy<Mutex<HashMap<&'static str, Regex>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
use std::sync::Mutex;
fn get_regex(pattern: &'static str) -> Result<Regex, regex::Error> {
{
let cache = REGEX_CACHE.lock().unwrap();
if let Some(re) = cache.get(pattern) {
return Ok(re.clone());
}
}
let re = Regex::new(pattern)?;
let mut cache = REGEX_CACHE.lock().unwrap();
cache.insert(pattern, re.clone());
Ok(re)
}
fn main() {
let email_re = get_regex(r"^[^@]+@[^@]+\.[^@]+$").unwrap();
println!("Is email: {}", email_re.is_match("test@example.com"));
println!("Is email: {}", email_re.is_match("invalid"));
}use once_cell::sync::Lazy;
use std::time::Duration;
// Simulated HTTP client
#[derive(Debug)]
struct HttpClient {
timeout: Duration,
base_url: String,
}
impl HttpClient {
fn new() -> Self {
println!("Creating HTTP client...");
Self {
timeout: Duration::from_secs(30),
base_url: "https://api.example.com".to_string(),
}
}
fn get(&self, path: &str) -> String {
format!("GET {}{}", self.base_url, path)
}
}
static HTTP_CLIENT: Lazy<HttpClient> = Lazy::new(HttpClient::new);
fn make_request(path: &str) -> String {
HTTP_CLIENT.get(path)
}
fn main() {
println!("Making requests...");
println!("{}", make_request("/users"));
println!("{}", make_request("/posts"));
}use once_cell::unsync::Lazy;
use std::cell::RefCell;
thread_local! {
static THREAD_STATE: Lazy<RefCell<Vec<String>>> = Lazy::new(|| {
println!("Initializing thread state for thread {:?}", std::thread::current().id());
RefCell::new(Vec::new())
});
}
fn add_item(item: &str) {
THREAD_STATE.with(|state| {
state.borrow_mut().push(item.to_string());
});
}
fn get_items() -> Vec<String> {
THREAD_STATE.with(|state| state.borrow().clone())
}
fn main() {
add_item("first");
add_item("second");
println!("Items: {:?}", get_items());
}use once_cell::sync::Lazy;
// Dependencies between lazy statics
static BASE_URL: Lazy<String> = Lazy::new(|| {
println!("Initializing BASE_URL");
"https://api.example.com".to_string()
});
static API_ENDPOINT: Lazy<String> = Lazy::new(|| {
println!("Initializing API_ENDPOINT");
format!("{}/v1/users", *BASE_URL)
});
static FULL_CONFIG: Lazy<String> = Lazy::new(|| {
println!("Initializing FULL_CONFIG");
format!("Endpoint: {}", *API_ENDPOINT)
});
fn main() {
// Access order determines initialization order
println!("Full config: {}", *FULL_CONFIG);
// This triggers: BASE_URL -> API_ENDPOINT -> FULL_CONFIG
}use once_cell::sync::OnceCell;
static CACHE: OnceCell<Vec<String>> = OnceCell::new();
fn get_or_compute() -> &'static Vec<String> {
CACHE.get_or_init(|| {
println!("Computing cache...");
(1..=5).map(|i| format!("item_{}", i)).collect()
})
}
fn try_initialize(value: Vec<String>) -> bool {
CACHE.set(value).is_ok()
}
fn main() {
// Try to set (succeeds if not already set)
try_initialize(vec!["preloaded".to_string()]);
// Get or compute
let items = get_or_compute();
println!("Items: {:?}", items);
// Get existing
if let Some(existing) = CACHE.get() {
println!("Already cached: {:?}", existing);
}
}use once_cell::unsync::OnceCell;
fn main() {
// unsync is faster when thread safety isn't needed
let cell = OnceCell::new();
// Set initial value
cell.set(42).unwrap();
// Get the value
println!("Value: {:?}", cell.get());
// Trying to set again fails
assert!(cell.set(100).is_err());
// get_mut for mutable access (only available on unsync)
if let Some(value) = cell.get_mut() {
*value *= 2;
}
println!("Modified: {:?}", cell.get());
// take() removes and returns the value
let taken = cell.take();
println!("Taken: {:?}", taken);
println!("After take: {:?}", cell.get());
}use once_cell::sync::Lazy;
enum Environment {
Development,
Production,
}
static ENV: Lazy<Environment> = Lazy::new(|| {
// Simulate environment detection
std::env::var("ENV").map(|_| Environment::Production).unwrap_or(Environment::Development)
});
static DEBUG_MODE: Lazy<bool> = Lazy::new(|| {
matches!(*ENV, Environment::Development)
});
fn main() {
println!("Debug mode: {}", *DEBUG_MODE);
}use once_cell::sync::Lazy;
use std::sync::Arc;
// Simulated connection pool
#[derive(Debug)]
struct ConnectionPool {
url: String,
max_connections: usize,
}
impl ConnectionPool {
fn new(url: &str, max: usize) -> Self {
println!("Creating connection pool to {}", url);
Self {
url: url.to_string(),
max_connections: max,
}
}
fn get_connection(&self) -> String {
format!("Connection to {}", self.url)
}
}
static POOL: Lazy<Arc<ConnectionPool>> = Lazy::new(|| {
Arc::new(ConnectionPool::new("postgresql://localhost/mydb", 10))
});
fn query(sql: &str) -> String {
let conn = POOL.get_connection();
format!("Executing '{}' on {}", sql, conn)
}
fn main() {
println!("{}", query("SELECT * FROM users"));
println!("{}", query("SELECT * FROM posts"));
}use once_cell::sync::OnceCell;
use std::sync::Mutex;
static PARSED_CONFIG: OnceCell<Result<Config, String>> = OnceCell::new();
#[derive(Debug, Clone)]
struct Config {
value: i32,
}
fn parse_config() -> Result<Config, String> {
// Simulate parsing
Ok(Config { value: 42 })
}
fn get_config() -> Result<&'static Config, &'static str> {
let result = PARSED_CONFIG.get_or_init(|| {
parse_config()
});
result.as_ref().map_err(|e| e.as_str())
}
fn main() {
match get_config() {
Ok(config) => println!("Config value: {}", config.value),
Err(e) => println!("Error: {}", e),
}
}use once_cell::sync::Lazy;
use std::collections::HashMap;
static WORD_FREQUENCIES: Lazy<HashMap<&'static str, usize>> = Lazy::new(|| {
let text = "the quick brown fox jumps over the lazy dog the fox";
let mut freq = HashMap::new();
for word in text.split_whitespace() {
*freq.entry(word).or_insert(0) += 1;
}
freq
});
fn word_count(word: &str) -> Option<usize> {
WORD_FREQUENCIES.get(word).copied()
}
fn main() {
println!("'the' appears {} times", word_count("the").unwrap_or(0));
println!("'fox' appears {} times", word_count("fox").unwrap_or(0));
println!("'cat' appears {} times", word_count("cat").unwrap_or(0));
}use once_cell::sync::OnceCell;
static STATE: OnceCell<String> = OnceCell::new();
fn is_initialized() -> bool {
STATE.get().is_some()
}
fn initialize(value: String) -> Result<(), String> {
STATE.set(value).map_err(|_| "Already initialized".to_string())
}
fn get_value() -> Option<&'static str> {
STATE.get().map(|s| s.as_str())
}
fn main() {
println!("Initialized: {}", is_initialized());
initialize("Hello".to_string()).unwrap();
println!("Initialized: {}", is_initialized());
// Try to initialize again
match initialize("World".to_string()) {
Ok(()) => println!("Initialized"),
Err(e) => println!("Error: {}", e),
}
println!("Value: {:?}", get_value());
}use once_cell::sync::Lazy;
use std::thread;
static DATA: Lazy<Vec<i32>> = Lazy::new(|| {
println!("Initializing DATA in thread {:?}", thread::current().id());
thread::sleep(std::time::Duration::from_millis(100));
vec![1, 2, 3, 4, 5]
});
fn main() {
let handles: Vec<_> = (0..3)
.map(|i| {
thread::spawn(move || {
println!("Thread {} accessing DATA", i);
let len = DATA.len();
println!("Thread {} got length: {}", i, len);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}// Cargo.toml:
// once_cell = { version = "1.0", features = ["serde"] }
use once_cell::sync::Lazy;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Settings {
app_name: String,
version: String,
}
static SETTINGS: Lazy<Settings> = Lazy::new(|| {
let json = r#"{"app_name":"MyApp","version":"1.0.0"}"#;
serde_json::from_str(json).expect("Failed to parse settings")
});
fn main() {
println!("App: {} v{}", SETTINGS.app_name, SETTINGS.version);
}Lazy<T> initializes value on first access, caches foreverOnceCell<T> for single-assignment optional initializationsync module for thread-safe, unsync for single-threadedLazy<T> derefs to T for transparent accessOnceCell::get_or_init() computes value if not setOnceCell::set() returns Err if already initializedunsync::OnceCell provides get_mut() and take() for mutationlazy_static!once_cell over lazy_static for new projects