




























std::sync::OnceLock 和 std::sync::LazyLock 是 Rust 标准库中用于线程安全延迟初始化(Lazy Initialization)的核心同步原语。它们在 Rust 1.70 和 1.80 中先后被正式稳定,旨在完美替代社区常用的第三方库 once_cell 和 lazy_static。
| 特性 | std::sync::LazyLock<T, F> | std::sync::OnceLock<T> |
|---|---|---|
| 初始化时机 | 创建时绑定闭包,首次解引用(Deref)时触发 |
创建时留空,后续显式调用 .get_or_init() 时触发 |
| 类型签名 | 包含闭包类型 F,写法较冗长 |
仅包含包裹的数据类型 T |
| 灵活性 | 只能使用创建时指定的全局唯一闭包 | 可以在不同地方传入不同的闭包或动态参数 |
| 典型场景 | 静态全局配置、正则表达式编译、数据库连接池 | 运行时动态确定的单例、带运行时入参的延迟初始化 |
LazyLock 采用结构一体化的设计思想,它将数据与其初始化闭包(生成函数)强绑定在一起。
当你声明一个 LazyLock 时,必须立即提供一个初始化函数。当代码第一次通过 * 运算符对其进行解引用时,该函数才会被执行:
use std::sync::LazyLock;
use std::collections::HashMap;
// 声明全局静态变量,自动推导闭包类型
static GLOBAL_MAP: LazyLock<HashMap<&'static str, i32>> = LazyLock::new(|| {
println!("Initializing GLOBAL_MAP..."); // 仅会打印一次
let mut m = HashMap::new();
m.insert("apple", 1);
m.insert("banana", 2);
m.into()
});
fn main() {
// 首次访问:触发初始化逻辑
println!("Apple price: {:?}", GLOBAL_MAP.get("apple"));
// 再次访问:直接读取缓存数据,不再执行闭包
println!("Banana price: {:?}", GLOBAL_MAP.get("banana"));
}
Deref):LazyLock 实现了 Deref trait。你不需要调用任何初始化方法,像普通变量一样使用它(或使用 *GLOBAL_MAP),Rust 编译器就会在底层悄悄地帮你安全地调用初始化逻辑。LazyLock 的完整类型其实是 LazyLock<T, F>,其中 F 是闭包的类型。由于在 static 变量中无法使用闭包类型推导,我们往往依赖 Rust 对标准库 LazyLock 的特殊简化支持,或者将初始化抽离为具体函数。OnceLock 采用容器化的设计思想。它初始化时是一个“空盒子”,你需要在稍后的代码中显式地往盒子里填入数据。
OnceLock 不在声明时绑定闭包,而是在运行时通过 .get_or_init() 方法传入初始化闭包:
use std::sync::OnceLock;
static RUNTIME_CONFIG: OnceLock<String> = OnceLock::new();
fn init_config(env: &str) {
// 根据运行时的入参来初始化全局变量
RUNTIME_CONFIG.get_or_init(|| {
format!("Config loaded for environment: {}", env)
});
}
fn main() {
// 模拟运行时的某种配置行为
init_config("production");
// 获取已经初始化好的值(若未初始化则会返回 None)
if let Some(config) = RUNTIME_CONFIG.get() {
println!("{}", config);
}
// 再次尝试用不同的闭包进行初始化,此处的闭包会被安全忽略,并返回已有的值
let value = RUNTIME_CONFIG.get_or_init(|| "fallback".to_string());
println!("Value: {}", value); // 依然输出 production 相关的配置
}
set(value):尝试直接填入值。如果盒子已经是满的,会返回 Err(value)。get():非阻塞地获取引用,如果没初始化则返回 None。get_or_init(|| ...):如果盒子空,执行闭包并填入;如果盒子满,直接返回已有值引用。get_or_try_init(|| ...):允许闭包返回一个 Result。如果闭包返回 Err,OnceLock 会保持未初始化状态,允许下一次重新尝试初始化。无论是 LazyLock 还是 OnceLock,其底层都建立在 std::sync::Once 原语之上。
Ordering::Acquire)直接读取数据,不再有任何锁竞争或阻塞开销。LazyLock 的场景:如果你要定义一个全局静态常量(如正则表达式 Regex、固定配置项),且它的初始化逻辑是绝对固定、不依赖运行时动态入参的。它的人体工程学体验最好,直接当成普通变量用即可。OnceLock 的场景:
port,或者读取出来的 config_path)。get_or_try_init 捕获错误并支持重试)。OnceLock 的类型签名没有闭包,对结构体更友好)。如果需要了解上述锁机制在单线程环境下的非线程安全高效替代品,可以随时询问标准库的 std::cell::LazyCell 和 std::cell::OnceCell。
参考资料:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。