lazy_static
lazy_static 是 Rust 中一个广泛使用的 crate,用于实现静态变量的延迟初始化(lazy initialization)。它允许在运行时执行代码来初始化静态变量,这在标准 static 无法处理的情况下非常有用,例如涉及堆分配(如 Vec 或 HashMap)、函数调用或环境变量读取的场景。lazy_static 通过宏确保初始化只发生一次,并支持线程安全。
1. 安装 lazy_static
在你的 Cargo.toml 文件中添加依赖:
[dependencies]
lazy_static = "1.5.0" # 使用最新版本,检查 crates.io 以获取更新
然后运行 cargo build 来安装。注意:lazy_static 支持 no_std 环境,通过启用 spin_no_std 特性。
2. 基本用法
使用 lazy_static! 宏声明静态变量。语法如下:
#![allow(unused)] fn main() { use lazy_static::lazy_static; lazy_static! { [pub] static ref NAME: TYPE = EXPR; } }
pub是可选的,用于公开可见性。- 支持属性,如文档注释。
- 类型必须实现
Sync以确保线程安全。 - 初始化表达式
EXPR在首次访问时执行,只执行一次。
访问时使用 *NAME 或通过 Deref 隐式解引用。宏内部生成一个实现 Deref<TYPE> 的独特类型,并使用原子操作确保线程安全。
3. 语义和实现细节
- 延迟初始化:首次解引用时评估
EXPR,后续访问返回相同对象。 - 线程安全:使用隐藏的静态变量和原子检查(
std::sync::Once或类似)。 - 死锁风险:如果多个 lazy static 相互依赖初始化,可能导致死锁。
- 属性:与标准
static类似,但不支持析构函数在进程退出时运行。 - 性能:每次访问有轻微原子开销,但基准测试显示在大多数情况下与手动初始化相当(约 26-27 ns/iter)。
4. 高级用法
- 手动初始化:使用
initialize(&NAME)强制初始化。 - 内部可变性:结合
Mutex或RwLock实现全局可变状态。 - No-std 支持:启用
spin_no_std特性,使用spincrate。 - LazyStatic trait:支持额外操作,如初始化检查。
- 多声明:宏中可声明多个静态变量。
5. 注意事项
- 类型必须是
Sync。 - 避免循环依赖以防死锁。
- 性能开销小,但在高频循环中可能累积;基准测试显示与
once_cell类似。 - 不要在函数内部使用
lazy_static!,因为它无法捕获动态环境;改为模块级别。 - 全局变量可能增加代码耦合;优先考虑局部变量或参数传递。
- 如果未使用,lazy static 不会初始化。
6. 替代方案
- Rust 1.70+ 的
std::sync::LazyLock:标准库替代,无需额外 crate。 once_cell:更灵活,支持非线程安全版本,可能更快。std::sync::Once:手动实现简单 lazy。- 对于常量,使用
const;对于可变,使用Mutex包装。
lazy_static 已被社区视为遗留;考虑迁移到标准库。
7. 20 个例子
以下是 20 个例子,从简单到复杂,覆盖配置、缓存、多线程、数学计算等实际场景。每个例子包括代码、输出(如果适用)和解释。假设已导入 use lazy_static::lazy_static;。
示例 1: 简单 HashMap 初始化
use std::collections::HashMap; lazy_static! { static ref HASHMAP: HashMap<u32, &'static str> = { let mut m = HashMap::new(); m.insert(0, "foo"); m.insert(1, "bar"); m }; } fn main() { println!("Value for 0: {}", HASHMAP.get(&0).unwrap()); }
输出:Value for 0: foo
解释:延迟构建 HashMap,只在首次访问时初始化。
示例 2: 从 HashMap 计算长度
use std::collections::HashMap; lazy_static! { static ref HASHMAP: HashMap<u32, &'static str> = { /* 如上 */ }; static ref COUNT: usize = HASHMAP.len(); } fn main() { println!("Map has {} entries.", *COUNT); }
输出:Map has 2 entries.
解释:依赖另一个 lazy static 计算值。
示例 3: 函数调用初始化
lazy_static! { static ref NUMBER: u32 = times_two(21); } fn times_two(n: u32) -> u32 { n * 2 } fn main() { println!("Result: {}", *NUMBER); }
输出:Result: 42
解释:运行时函数调用。
示例 4: 带文档注释
lazy_static! { /// 示例文档 static ref EXAMPLE: u8 = 42; } fn main() { println!("Value: {}", *EXAMPLE); }
输出:Value: 42
解释:支持属性和文档。
示例 5: Regex 缓存
use regex::Regex; lazy_static! { static ref DATE_REGEX: Regex = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); } fn main() { println!("Is match: {}", DATE_REGEX.is_match("2025-09-03")); }
输出:Is match: true
解释:避免重复编译 Regex,提高性能。
示例 6: 全局 Mutex 可变向量
use std::sync::Mutex; lazy_static! { static ref NAMES: Mutex<Vec<String>> = Mutex::new(vec!["Alice".to_string()]); } fn main() { NAMES.lock().unwrap().push("Bob".to_string()); println!("Names: {:?}", *NAMES.lock().unwrap()); }
输出:Names: ["Alice", "Bob"]
解释:线程安全全局可变状态。
示例 7: 环境变量读取
use std::env; lazy_static! { static ref LANG: String = env::var("LANG").unwrap_or("en_US".to_string()); } fn main() { println!("Language: {}", *LANG); }
解释:运行时读取环境变量。
示例 8: 数学计算
lazy_static! { static ref ROOT_OF_SEVEN: f64 = 7_f64.sqrt(); } fn main() { println!("√7: {:.2}", *ROOT_OF_SEVEN); }
输出:√7: 2.65
解释:非常量数学操作。
示例 9: 全局计数器
use std::sync::Mutex; lazy_static! { static ref COUNTER: Mutex<u32> = Mutex::new(0); } fn increment() { *COUNTER.lock().unwrap() += 1; } fn main() { increment(); println!("Counter: {}", *COUNTER.lock().unwrap()); }
输出:Counter: 1
解释:线程安全计数器。
示例 10: 配置加载
lazy_static! { static ref CONFIG: String = "config_value".to_string(); // 模拟从文件加载 } fn main() { println!("Config: {}", *CONFIG); }
解释:延迟加载配置。
示例 11: 模拟数据库连接
use std::sync::Mutex; struct DbPool; // 模拟 lazy_static! { static ref DB_POOL: Mutex<DbPool> = Mutex::new(DbPool); } fn main() { let _ = DB_POOL.lock().unwrap(); println!("DB connected."); }
解释:全局连接池。
示例 12: 与 Arc 结合
use std::sync::{Arc, Mutex}; lazy_static! { static ref SHARED: Arc<Mutex<u32>> = Arc::new(Mutex::new(42)); } fn main() { println!("Shared: {}", *SHARED.lock().unwrap()); }
解释:共享所有权。
示例 13: 手动初始化
lazy_static! { static ref DATA: String = "Initialized".to_string(); } fn main() { lazy_static::initialize(&DATA); println!("{}", *DATA); }
解释:强制提前初始化。
示例 14: No-std 环境(需启用 spin_no_std)
#![allow(unused)] fn main() { // 在 no_std 项目中 lazy_static! { static ref VALUE: u32 = 42; } }
解释:无标准库支持。
示例 15: 多声明
lazy_static! { static ref A: u32 = 1; static ref B: u32 = 2; } fn main() { println!("A + B: {}", *A + *B); }
输出:A + B: 3
解释:宏中多个静态。
示例 16: 公共静态
#![allow(unused)] fn main() { lazy_static! { pub static ref PUBLIC: u32 = 42; } }
解释:模块间可见。
示例 17: 自定义结构体
#[derive(Debug)] struct MyStruct { value: u32 } lazy_static! { static ref STRUCT: MyStruct = MyStruct { value: 42 }; } fn main() { println!("{:?}", *STRUCT); }
解释:自定义类型。
示例 18: 缓存昂贵计算
fn expensive() -> u32 { /* 模拟耗时 */ 42 } lazy_static! { static ref CACHE: u32 = expensive(); } fn main() { println!("Cached: {}", *CACHE); }
解释:避免重复计算。
示例 19: 单例模式
struct Singleton; lazy_static! { static ref INSTANCE: Singleton = Singleton; } fn main() { let _ = &*INSTANCE; println!("Singleton accessed."); }
解释:实现单例。
示例 20: 全局日志器
use std::sync::Mutex; #[derive(Debug)] struct Logger { logs: Mutex<Vec<String>> } lazy_static! { static ref LOGGER: Logger = Logger { logs: Mutex::new(vec![]) }; } fn log(msg: &str) { LOGGER.logs.lock().unwrap().push(msg.to_string()); } fn main() { log("Hello"); println!("Logs: {:?}", *LOGGER.logs.lock().unwrap()); }
输出:Logs: ["Hello"]
解释:线程安全日志。