Loading pageā¦
Rust walkthroughs
Loading pageā¦
lazy_static! macro achieve compile-time verification of static initialization safety?lazy_static! achieves compile-time verification by wrapping static initialization in a type-checked module that enforces Rust's normal safety rules on the initialization expression, while deferring actual execution to runtime with thread-safe one-time initialization. The macro generates a struct with a Deref implementation and uses sync::Once for thread-safe initialization. At compile time, Rust verifies that the initialization expression is well-typed and that any unsafe operations within it are explicitly marked, but the actual initialization code executes lazily at first access. This combination provides compile-time guarantees about type safety and borrow checking while enabling runtime initialization of values that cannot be computed statically.
use lazy_static::lazy_static;
fn basic_usage() {
lazy_static! {
static ref CONFIG: Vec<String> = {
let mut v = Vec::new();
v.push("host".to_string());
v.push("port".to_string());
v
};
}
// First access initializes CONFIG
println!("{:?}", *CONFIG);
// Subsequent accesses reuse the initialized value
println!("{:?}", *CONFIG);
}The static is initialized on first access, not at program start.
use lazy_static::lazy_static;
fn compile_time_verification() {
// Type checking is enforced
lazy_static! {
static ref NUMBERS: Vec<i32> = vec
![1, 2, 3];
// Type annotation Vec<i32> must match expression type
}
// Borrow checking applies to initialization expression
lazy_static! {
static ref NAME: String = {
let s = "hello".to_string();
s + " world" // Owned value returned
};
}
// Lifetime checking applies
lazy_static! {
static ref COUNT: i32 = {
let x = 42;
x // Copy type, no lifetime issues
};
}
// All normal Rust rules apply to the initialization block
}The initialization expression undergoes full type checking, borrow checking, and lifetime analysis.
use lazy_static::lazy_static;
use std::sync::Once;
use std::cell::UnsafeCell;
fn generated_code_structure() {
// What lazy_static! generates:
// Input:
lazy_static! {
static ref VALUE: i32 = 42;
}
// Approximate generated output:
// static VALUE: Lazy<i32> = Lazy {
// cell: UnsafeCell::new(None),
// once: Once::new(),
// };
//
// impl Deref for Lazy<i32> {
// fn deref(&self) -> &i32 {
// self.once.call_once(|| {
// unsafe {
// *self.cell.get() = Some(42);
// }
// });
// unsafe { &*self.cell.get() }.as_ref().unwrap()
// }
// }
// The static itself is valid at compile time
// The initialization expression is type-checked at compile time
// The actual initialization happens at runtime
}The macro generates a static struct with lazy initialization logic.
use lazy_static::lazy_static;
use std::thread;
fn thread_safety() {
lazy_static! {
static ref SHARED: Vec<i32> = {
println!("Initializing SHARED");
vec
![1, 2, 3]
};
}
let handles: Vec<_> = (0..10)
.map(|_| {
thread::spawn(|| {
// Multiple threads might try to initialize simultaneously
// sync::Once ensures initialization happens exactly once
let _ = *SHARED;
})
})
.collect();
for h in handles {
h.join().unwrap();
}
// Output: "Initializing SHARED" printed exactly once
// even with concurrent first access
}The sync::Once primitive ensures initialization happens exactly once, even under concurrent access.
use lazy_static::lazy_static;
fn non_const_initialization() {
// These cannot be regular statics because they need runtime initialization:
lazy_static! {
// Heap allocation
static ref VEC: Vec<i32> = vec
![1, 2, 3];
// String allocation
static ref STRING: String = "hello".to_string();
// File I/O
static ref CONFIG: String = std::fs::read_to_string("config.txt")
.unwrap_or_default();
// Network call (hypothetical)
// static ref ADDR: SocketAddr = resolve_address();
}
// Regular statics require const expressions:
// static VEC: Vec<i32> = vec
![1, 2, 3]; // ERROR: vec! is not const
// lazy_static! allows non-const initialization
// by deferring to runtime
}lazy_static! enables statics with non-const initialization expressions.
use lazy_static::lazy_static;
fn type_checking() {
lazy_static! {
// Correct type annotation
static ref NUMBERS: Vec<i32> = vec
![1, 2, 3];
// Type mismatch caught at compile time
// static ref WRONG: Vec<i32> = "hello";
// ERROR: mismatched types
}
// The initialization expression must match the type annotation
// This is verified at compile time, not runtime
}Type mismatches are caught at compile time by the macro's type annotation requirements.
use lazy_static::lazy_static;
fn borrow_checking() {
// The initialization block is borrow-checked at compile time
lazy_static! {
static ref DATA: String = {
let s = String::from("hello");
let borrowed = &s;
// ERROR: s is borrowed
// s // Would error: value borrowed after move
borrowed.clone() // OK: clone returns owned value
};
}
lazy_static! {
static ref VALID: String = {
let s = String::from("hello");
s + " world" // OK: s is consumed, new owned value returned
};
}
// All borrow checking rules apply within the initialization block
}Borrow checking applies to the initialization expression at compile time.
use lazy_static::lazy_static;
use std::sync::Mutex;
fn sync_requirement() {
// The value type must be Sync for lazy_static!
// because multiple threads can access it
lazy_static! {
// Vec<i32> is Sync (no interior mutability)
static ref VEC: Vec<i32> = vec
![1, 2, 3];
// Mutex<T> makes non-Sync types accessible
static ref COUNTER: Mutex<i32> = Mutex::new(0);
}
// This would fail to compile:
// lazy_static! {
// static ref RC: std::rc::Rc<i32> = std::rc::Rc::new(42);
// }
// ERROR: Rc<i32> is not Sync
// The Sync bound is enforced at compile time
}The generated static must implement Sync, which is verified at compile time.
use lazy_static::lazy_static;
use std::sync::Mutex;
use std::cell::RefCell;
fn interior_mutability() {
// Mutex provides interior mutability with Sync
lazy_static! {
static ref STATE: Mutex<Vec<String>> = Mutex::new(Vec::new());
}
// Safe mutation through Mutex
STATE.lock().unwrap().push("hello".to_string());
// RefCell is NOT Sync, so cannot be used directly
// lazy_static! {
// static ref REF_CELL: RefCell<i32> = RefCell::new(42);
// }
// ERROR: RefCell is not Sync
// thread_local! for non-Sync types
thread_local! {
static LOCAL: RefCell<i32> = RefCell::new(42);
}
}For interior mutability in lazy statics, use Mutex or RwLock, not RefCell.
use lazy_static::lazy_static;
fn initialization_order() {
lazy_static! {
static ref FIRST: i32 = {
println!("First initialized");
1
};
static ref SECOND: i32 = {
println!("Second initialized");
*FIRST + 1 // Depends on FIRST
};
}
// Access order determines initialization order
println!("Accessing SECOND");
let _ = *SECOND; // Initializes FIRST, then SECOND
// Output:
// Accessing SECOND
// First initialized
// Second initialized
// Circular dependencies cause deadlock:
// lazy_static! {
// static ref A: i32 = *B;
// static ref B: i32 = *A;
// }
// Accessing either would deadlock
}Dependencies between lazy statics are initialized in access order, but circular dependencies deadlock.
use lazy_static::lazy_static;
use std::sync::Once;
fn error_handling() {
// Initialization that can fail:
lazy_static! {
static ref CONFIG: String = std::fs::read_to_string("config.txt")
.expect("Failed to read config");
}
// If initialization panics:
// - The panic propagates to the accessing thread
// - The Once cell is "poisoned"
// - Subsequent accesses panic with "previously panicked"
// Better pattern: handle errors gracefully
lazy_static! {
static ref CONFIG_RESULT: Result<String, std::io::Error> =
std::fs::read_to_string("config.txt");
}
match CONFIG_RESULT.as_ref() {
Ok(config) => println!("Config: {}", config),
Err(e) => eprintln!("Error loading config: {}", e),
}
}Panics during initialization poison the lazy static, causing all future accesses to panic.
use lazy_static::lazy_static;
use std::ops::Deref;
fn deref_access() {
lazy_static! {
static ref VALUE: String = "hello".to_string();
}
// Deref to access the value
let s: &String = &*VALUE; // VALUE.deref()
// Automatic deref for methods
println!("Length: {}", VALUE.len()); // VALUE.deref().len()
// Explicit deref
let s: &str = &*VALUE;
// The Lazy type implements Deref
// This is how lazy_static! provides access to the value
}The generated struct implements Deref, providing transparent access to the underlying value.
use lazy_static::lazy_static;
mod my_module {
// Public lazy static
lazy_static! {
pub static ref PUBLIC: i32 = 42;
}
// Private lazy static
lazy_static! {
static ref PRIVATE: i32 = {
println!("Private initialized");
100
};
}
pub fn use_private() -> i32 {
*PRIVATE
}
}
fn visibility() {
// Access public static from another module
let _ = *my_module::PUBLIC;
// Cannot access private static
// let _ = *my_module::PRIVATE; // ERROR: private
// But functions in the module can use it
let _ = my_module::use_private();
}The macro respects visibility rules for the generated static.
use lazy_static::lazy_static;
fn compare_to_const() {
// const: evaluated at compile time
const CONST_VALUE: i32 = 42;
// Must be const expression
// static: evaluated at program start
// static STATIC_VALUE: i32 = 42; // Must be const expression too
// lazy_static: evaluated at first access
lazy_static! {
static ref LAZY_VALUE: i32 = {
// Can have arbitrary code
let x = 42;
x * 2
};
}
// const: no runtime cost, but limited to const expressions
// static: no runtime cost, but limited to const expressions
// lazy_static: runtime cost on first access, but arbitrary expressions
// Compile-time verification for all three:
// - Type checking
// - Const/static have additional const evaluation rules
// - lazy_static has normal Rust expression rules
}lazy_static! trades compile-time evaluation for flexible initialization.
use lazy_static::lazy_static;
fn compile_time_guarantees() {
// What's verified at compile time:
lazy_static! {
static ref VALUE: String = {
// 1. Type checking: block must return String
"hello".to_string()
};
static ref BORROWED: String = {
// 2. Borrow checking: normal rules apply
let s = "world".to_string();
s // Ownership transferred
};
static ref SYNC_CHECK: Vec<i32> = {
// 3. Sync bound: Vec<i32> must implement Sync
vec
![1, 2, 3]
};
}
// 4. Visibility: pub/static rules apply
// What's NOT verified at compile time:
// - Whether initialization will succeed (file I/O, etc.)
// - Whether initialization will panic
// - Thread-safety of the initialization expression itself
// What's DEFERRED to runtime:
// - Actual execution of initialization block
// - Memory allocation
// - Any side effects (I/O, network, etc.)
}Compile-time covers type/borrow checking; runtime covers initialization execution.
use lazy_static::lazy_static;
fn macro_expansion() {
// Input:
lazy_static! {
static ref EXAMPLE: String = "hello".to_string();
}
// Simplified expansion:
//
// static EXAMPLE: Lazy<String> = Lazy::new(|| {
// "hello".to_string()
// });
//
// struct Lazy<T> {
// cell: UnsafeCell<Option<T>>,
// once: Once,
// }
//
// impl<T> Deref for Lazy<T> {
// type Target = T;
//
// fn deref(&self) -> &T {
// self.once.call_once(|| {
// let value = (self.init)();
// unsafe { *self.cell.get() = Some(value); }
// });
// unsafe { (*self.cell.get()).as_ref().unwrap() }
// }
// }
// Key compile-time guarantees:
// 1. Lazy<T> struct type-checks
// 2. The init closure type-checks: || -> T
// 3. T implements Sync (enforced by Lazy)
// 4. Static lifetime rules apply
}The macro generates type-checked code that enforces all safety rules.
use lazy_static::lazy_static;
fn unsafe_in_init() {
lazy_static! {
static ref UNSAFE_VALUE: i32 = unsafe {
// Unsafe code allowed within the block
// But must be explicitly marked unsafe
let ptr: *const i32 = &42;
*ptr
};
}
// The initialization expression is normal Rust code
// Unsafe operations must be in unsafe blocks
// This is verified at compile time
// This would fail to compile:
// lazy_static! {
// static ref BAD: i32 = {
// let ptr: *const i32 = &42;
// *ptr // ERROR: dereference of raw pointer is unsafe
// };
// }
}Unsafe code within initialization must be in unsafe blocksāverified at compile time.
use lazy_static::lazy_static;
fn static_lifetime() {
lazy_static! {
// The returned reference has 'static lifetime
static ref STATIC_REF: &'static str = {
// String literal has 'static lifetime
"hello world"
};
// References must be 'static
// static ref NON_STATIC: &str = {
// let s = String::from("hello");
// &s // ERROR: s does not live long enough
// };
}
// The lazy static itself lives for 'static
// References obtained from it have 'static lifetime
fn get_ref() -> &'static str {
&*STATIC_REF // Lives for 'static
}
}Values in lazy statics have 'static lifetimeāborrow checking enforces this at compile time.
use lazy_static::lazy_static;
fn memory_location() {
lazy_static! {
static ref VALUE: String = "hello".to_string();
}
// The Lazy struct is in static memory (data segment)
// The actual String value is heap-allocated
// Before initialization:
// - Lazy struct exists in static memory
// - cell contains None
// - Value not yet allocated
// After initialization:
// - Lazy struct still in static memory
// - cell contains Some(String)
// - String data on heap
// The 'static lifetime refers to the Lazy struct location
// Not the heap-allocated String data
let ptr = &*VALUE as *const String;
// This pointer is valid for 'static
// But the heap location may be different from the Lazy location
}The lazy static struct lives in static memory; the value lives wherever the initialization expression places it.
use lazy_static::lazy_static;
use std::sync::Once;
fn once_cell_pattern() {
// lazy_static! uses the Once pattern internally
lazy_static! {
static ref VALUE: i32 = {
println!("Initializing");
42
};
}
// Equivalent manual pattern:
static ONCE: Once = Once::new();
static mut VALUE_PTR: Option<i32> = None;
fn get_value() -> &'static i32 {
ONCE.call_once(|| {
println!("Initializing manually");
unsafe {
VALUE_PTR = Some(42);
}
});
unsafe { VALUE_PTR.as_ref().unwrap() }
}
// lazy_static! encapsulates this pattern safely
// The Once ensures:
// 1. Initialization happens exactly once
// 2. Thread-safe synchronization
// 3. Memory ordering guarantees
}The Once primitive provides thread-safe one-time initialization.
use lazy_static::lazy_static;
use std::sync::OnceLock;
fn modern_alternative() {
// lazy_static! macro approach
lazy_static! {
static ref OLD_STYLE: String = "hello".to_string();
}
// Modern once_cell (now in std):
static NEW_STYLE: OnceLock<String> = OnceLock::new();
// or lazier:
static NEW_STYLE_LAZY: std::sync::LazyLock<String> =
std::sync::LazyLock::new(|| "hello".to_string());
// once_cell provides:
// - Same thread-safe one-time initialization
// - No macro needed
// - Better type inference
// - More flexible initialization patterns
// lazy_static! still useful for:
// - Simple cases with macro syntax
// - Legacy codebases
// - Cases where you want all statics grouped
}Modern Rust has OnceLock and LazyLock in std, reducing need for lazy_static!.
Compile-time verification provided:
// 1. Type checking
// - Type annotation must match expression type
// - Verified at compile time
// 2. Borrow checking
// - Normal borrow rules apply to initialization block
// - No dangling references, correct ownership
// 3. Sync bound
// - Type must implement Sync
// - Enforced by the generated struct's impl
// 4. Lifetime checking
// - References must have 'static lifetime
// - Or be owned types
// 5. Visibility rules
// - pub/private respected
// - Module scoping enforcedRuntime behavior:
// 1. Lazy evaluation
// - Initialization on first access
// - Not at program start
// 2. Thread-safe one-time init
// - sync::Once ensures single initialization
// - Concurrent access is safe
// 3. Potential panics
// - Initialization code can panic
// - Panics poison the static
// 4. Memory allocation
// - Heap allocation at initialization time
// - Not at compile timeWhat makes it "safe":
// The macro ensures:
// 1. Initialization expression type-checks (compile time)
// 2. Thread-safe execution (runtime)
// 3. Single initialization (runtime)
// 4. 'static lifetime for references (compile time)
// 5. Sync bound for shared access (compile time)
// What it doesn't prevent:
// - Panics during initialization (runtime failure)
// - Circular dependencies (runtime deadlock)
// - Poisoned state after panic (runtime failure)Key insight: lazy_static! provides compile-time safety verification through Rust's normal type system and borrow checker, applied to the initialization expression. The macro's generated code wraps the initialization in a struct with appropriate bounds (Sync), uses Once for thread-safe initialization, and implements Deref for transparent access. The "lazy" part is runtime behaviorācompile-time verification is the same as any other Rust code. This gives you the safety guarantees of static analysis while enabling dynamic initialization patterns that would otherwise be impossible with Rust's const evaluation rules.