
Rust Once 、OnceLock、LazyLock 一次性初始化详解
-
- [1、 引言](#1、 引言)
- [2、 为什么需要一次性初始化](#2、 为什么需要一次性初始化)
-
- [2.1、 全局可变状态的困境](#2.1、 全局可变状态的困境)
- [2.2、 常见替代方案的缺陷](#2.2、 常见替代方案的缺陷)
- [3、 Once:只执行一次的同步原语](#3、 Once:只执行一次的同步原语)
-
- [3.1、 基本概念](#3.1、 基本概念)
- [3.2、 基础用法](#3.2、 基础用法)
- [3.3 、与全局数据配合](#3.3 、与全局数据配合)
- [3.4 、适用场景](#3.4 、适用场景)
- 4、OnceLock<T>:可存储数据的一次性容器
-
- [4.1、 基本概念](#4.1、 基本概念)
- [4.2、 基础用法](#4.2、 基础用法)
- [4.3 、线程安全保证](#4.3 、线程安全保证)
- [4.4 、常用方法](#4.4 、常用方法)
- [4.5、 适用场景](#4.5、 适用场景)
- [5、 LazyLock<T>:更简洁的懒加载](#5、 LazyLock
:更简洁的懒加载) -
- [5.1、 基本概念](#5.1、 基本概念)
- [5.2 、基础用法](#5.2 、基础用法)
- [5.3、 与 OnceLock 的对比](#5.3、 与 OnceLock 的对比)
- [5.4、 适用场景](#5.4、 适用场景)
- [6、 三者对比与选型](#6、 三者对比与选型)
- [7、 实战:全局配置管理器](#7、 实战:全局配置管理器)
- 8、注意事项与常见陷阱
-
- [8.1、 初始化闭包中的递归调用](#8.1、 初始化闭包中的递归调用)
- [8.2 、不要用 OnceLock 存储非 Send 类型](#8.2 、不要用 OnceLock 存储非 Send 类型)
- [8.3、 初始化开销](#8.3、 初始化开销)
- [9、 总结](#9、 总结)
- 二、代码示例

1、 引言
在 Rust 中,很多场景需要「只初始化一次」的全局或共享数据:全局配置、单例连接池、懒加载的静态资源等。如果直接用 static mut 或裸指针,会引入 unsafe 和数据竞争风险;如果每次访问都重新计算,又会浪费性能。
Rust 标准库为此提供了一套安全、高效的一次性初始化工具:Once、OnceLock<T> 与 LazyLock<T>。本文将从原理到实战,逐一讲解它们的用法、区别与适用场景。
2、 为什么需要一次性初始化
2.1、 全局可变状态的困境
Rust 的 static 变量要求类型实现 Sync,且默认不可变。要存放运行时才能确定的值(如读取配置文件、建立数据库连接),直接写会遇到困难:
rust
// 编译失败:static 变量要求编译期常量
static CONFIG: HashMap<String, String> = HashMap::new();
2.2、 常见替代方案的缺陷
| 方案 | 问题 |
|---|---|
static mut |
需要 unsafe,易引发数据竞争 |
lazy_static! 宏 |
第三方依赖,且宏写法不够直观 |
std::sync::Once |
只解决「执行一次」,不直接保存数据 |
OnceLock<T> / LazyLock<T> |
标准库原生支持,安全且简洁 |
3、 Once:只执行一次的同步原语
3.1、 基本概念
std::sync::Once 是最底层的一次性初始化原语。它保证传入的闭包在整个程序生命周期内只执行一次,即使多个线程同时调用。
3.2、 基础用法
rust
use std::sync::Once;
static INIT: Once = Once::new();
fn init() {
// 只执行一次的初始化逻辑
println!("初始化全局资源");
}
fn main() {
// 多个线程同时调用也只会执行一次
INIT.call_once(|| {
init();
});
}
3.3 、与全局数据配合
Once 本身不保存数据,通常与 static mut 或 UnsafeCell 配合使用,由 Once 保证初始化只发生一次:
rust
use std::sync::Once;
use std::sync::Mutex;
static mut CONNECTION: Option<Mutex<String>> = None;
static INIT: Once = Once::new();
fn get_connection() -> &'static Mutex<String> {
INIT.call_once(|| {
unsafe {
CONNECTION = Some(Mutex::new(String::from("数据库连接")));
}
});
unsafe { CONNECTION.as_ref().unwrap() }
}
注意:上面的写法仍然涉及
unsafe。在绝大多数场景下,我们更推荐使用OnceLock<T>来避免手写unsafe。
3.4 、适用场景
- 只需要「执行一次」的副作用,如注册全局钩子、初始化日志系统。
- 作为更高级封装(如
OnceLock)的内部实现基础。
4、OnceLock<T>:可存储数据的一次性容器
4.1、 基本概念
std::sync::OnceLock<T> 在 Once 的基础上增加了数据存储能力 。它内部使用 Once 保证初始化只执行一次,并通过 UnsafeCell 安全地保存数据。
4.2、 基础用法
rust
use std::sync::OnceLock;
static CONFIG: OnceLock<String> = OnceLock::new();
fn get_config() -> &'static str {
CONFIG.get_or_init(|| {
// 首次调用时执行,后续直接返回缓存值
String::from("server=127.0.0.1;port=8080")
})
}
fn main() {
println!("配置: {}", get_config());
println!("再次获取: {}", get_config()); // 不会重复初始化
}
4.3 、线程安全保证
OnceLock<T> 实现了 Sync(当 T: Send + Sync),多个线程可以安全地并发访问:
rust
use std::sync::OnceLock;
use std::thread;
static COUNTER: OnceLock<u64> = OnceLock::new();
fn main() {
let handles: Vec<_> = (0..10)
.map(|i| {
thread::spawn(move || {
let value = COUNTER.get_or_init(|| i * 100);
println!("线程 {} 获取到: {}", i, value);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
4.4 、常用方法
| 方法 | 说明 |
|---|---|
get() |
获取已初始化的值,未初始化返回 None |
get_or_init(f) |
获取值,未初始化则执行闭包并缓存 |
get_or_try_init(f) |
初始化可能失败的版本,返回 Result |
set(value) |
手动设置值,已初始化则返回 Err |
into_inner() |
取出内部值(消费 OnceLock) |
4.5、 适用场景
- 全局单例配置、连接池、缓存。
- 需要「首次访问时懒加载」的静态数据。
- 希望避免
unsafe的线程安全初始化。
5、 LazyLock<T>:更简洁的懒加载
5.1、 基本概念
std::sync::LazyLock<T> 是 OnceLock<T> 的语法糖封装,在 Rust 1.80 版本稳定。它允许在定义静态变量时直接传入初始化闭包,访问时自动完成懒加载。
5.2 、基础用法
rust
use std::sync::LazyLock;
static CONFIG: LazyLock<String> = LazyLock::new(|| {
// 首次访问时执行
String::from("server=127.0.0.1;port=8080")
});
fn main() {
// 首次访问触发初始化
println!("配置: {}", *CONFIG);
// 后续访问直接复用
println!("再次访问: {}", *CONFIG);
}
5.3、 与 OnceLock 的对比
rust
use std::sync::{LazyLock, OnceLock};
// OnceLock 写法:需要手动调用 get_or_init
static A: OnceLock<Vec<u32>> = OnceLock::new();
fn get_a() -> &'static Vec<u32> {
A.get_or_init(|| vec![1, 2, 3])
}
// LazyLock 写法:定义时直接指定初始化逻辑
static B: LazyLock<Vec<u32>> = LazyLock::new(|| vec![1, 2, 3]);
fn main() {
println!("A: {:?}", get_a());
println!("B: {:?}", *B);
}
5.4、 适用场景
- 静态变量的懒加载初始化,代码更简洁。
- 初始化逻辑在定义时就能确定,无需运行时动态决定。
6、 三者对比与选型
| 特性 | Once |
OnceLock<T> |
LazyLock<T> |
|---|---|---|---|
| 存储数据 | 否 | 是 | 是 |
需要 unsafe |
配合使用时需要 | 不需要 | 不需要 |
| 初始化时机 | 手动调用 call_once |
首次 get_or_init |
首次解引用 |
| 写法简洁度 | 低 | 中 | 高 |
| 稳定版本 | 1.0 | 1.70 | 1.80 |
| 适用场景 | 纯副作用初始化 | 需要懒加载的全局数据 | 定义时即可确定初始化逻辑 |
选型建议
- 只需要「执行一次」的副作用 → 用
Once。 - 需要保存数据、首次访问时初始化 → 用
OnceLock<T>。 - 静态变量初始化逻辑简单明确 → 用
LazyLock<T>,代码最简洁。
7、 实战:全局配置管理器
下面是一个综合示例,演示如何用 LazyLock 实现一个线程安全的全局配置管理器:
rust
use std::collections::HashMap;
use std::sync::LazyLock;
static CONFIG: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
// 模拟从配置文件读取
let mut map = HashMap::new();
map.insert("host".to_string(), "127.0.0.1".to_string());
map.insert("port".to_string(), "8080".to_string());
map.insert("timeout".to_string(), "30".to_string());
map
});
fn get_config(key: &str) -> Option<&'static str> {
CONFIG.get(key).map(|s| s.as_str())
}
fn main() {
println!("host = {}", get_config("host").unwrap());
println!("port = {}", get_config("port").unwrap());
println!("timeout = {}", get_config("timeout").unwrap());
}
8、注意事项与常见陷阱
8.1、 初始化闭包中的递归调用
在 get_or_init 的闭包中再次访问同一个 OnceLock 会导致死锁:
rust
use std::sync::OnceLock;
static DATA: OnceLock<u32> = OnceLock::new();
fn main() {
// 危险:闭包内再次调用 get_or_init 会死锁
let _ = DATA.get_or_init(|| {
*DATA.get_or_init(|| 42) + 1
});
}
8.2 、不要用 OnceLock 存储非 Send 类型
OnceLock<T> 要求 T: Send + Sync 才能跨线程共享。如果只需要单线程使用,可以考虑 std::cell::OnceCell<T>。
8.3、 初始化开销
OnceLock 和 LazyLock 的首次访问有少量原子操作开销,但后续访问几乎零成本。对于高频访问的全局数据,这是非常划算的。
9、 总结
Once是最底层的一次性执行原语,适合纯副作用初始化。OnceLock<T>在Once基础上增加了数据存储,是线程安全懒加载的首选。LazyLock<T>是OnceLock<T>的简洁封装,适合静态变量初始化。- 三者都基于标准库实现,无需第三方依赖,安全且高效。
在实际项目中,优先考虑 LazyLock<T> 或 OnceLock<T>,尽量避免手写 unsafe 的初始化逻辑。
二、代码示例
rust
use std::sync::{Once, OnceLock, LazyLock};
use std::sync::atomic::{AtomicU32, Ordering};
use std::thread;
// ====================== Once ======================
static G_ONCE: Once = Once::new();
static G_ONCE_VALUE: AtomicU32 = AtomicU32::new(0);
fn demo_once() {
let mut handles = Vec::new();
for tid in 0..4 {
let h = thread::spawn(move || {
println!("[{}] 尝试执行 Once 初始化", tid);
G_ONCE.call_once(|| {
println!("==== Once 初始化逻辑(仅运行1次)====");
G_ONCE_VALUE.store(100, Ordering::Release);
});
let val = G_ONCE_VALUE.load(Ordering::Acquire);
println!("[{}] Once读取值 = {}", tid, val);
});
handles.push(h);
}
for h in handles {
h.join().unwrap();
}
}
// ====================== OnceLock<T> ======================
static G_ONCE_LOCK: OnceLock<String> = OnceLock::new();
fn demo_once_lock() {
let mut handles = Vec::new();
for tid in 0..4 {
let h = thread::spawn(move || {
println!("[{}] OnceLock 请求获取/初始化", tid);
let val = G_ONCE_LOCK.get_or_init(|| {
println!("==== OnceLock 初始化闭包(仅1次)====");
format!("once_lock_data_{}", 999)
});
println!("[{}] OnceLock value = {}", tid, val);
});
handles.push(h);
}
for h in handles {
h.join().unwrap();
}
println!("OnceLock 是否初始化完成: {}", G_ONCE_LOCK.get().is_some());
if let Some(v) = G_ONCE_LOCK.get() {
println!("get() 读取: {}", v);
}
}
// ====================== LazyLock<T> ======================
static G_LAZY: LazyLock<Vec<u32>> = LazyLock::new(|| {
println!("==== LazyLock 自动初始化(第一次解引用时执行)====");
vec![1, 2, 3, 4, 5]
});
fn demo_lazy_lock() {
let mut handles = Vec::new();
for tid in 0..4 {
let h = thread::spawn(move || {
println!("[{}] LazyLock 访问", tid);
let data = &*G_LAZY;
println!("[{}] LazyLock data = {:?}", tid, data);
});
handles.push(h);
}
for h in handles {
h.join().unwrap();
}
}
fn main() {
println!("========= Demo Once =========");
demo_once();
println!("\n========= Demo OnceLock =========");
demo_once_lock();
println!("\n========= Demo LazyLock =========");
demo_lazy_lock();
}
rust
PS E:\leaning\slint\test> cargo run
========= Demo Once =========
[0] 尝试执行 Once 初始化
==== Once 初始化逻辑(仅运行1次)====
[0] Once读取值 = 100
[1] 尝试执行 Once 初始化
[1] Once读取值 = 100
[3] 尝试执行 Once 初始化
[3] Once读取值 = 100
[2] 尝试执行 Once 初始化
[2] Once读取值 = 100
========= Demo OnceLock =========
[0] OnceLock 请求获取/初始化
==== OnceLock 初始化闭包(仅1次)====
[0] OnceLock value = once_lock_data_999
[2] OnceLock 请求获取/初始化
[2] OnceLock value = once_lock_data_999
[1] OnceLock 请求获取/初始化
[1] OnceLock value = once_lock_data_999
[3] OnceLock 请求获取/初始化
[3] OnceLock value = once_lock_data_999
OnceLock 是否初始化完成: true
get() 读取: once_lock_data_999
========= Demo LazyLock =========
[0] LazyLock 访问
==== LazyLock 自动初始化(第一次解引用时执行)====
[0] LazyLock data = [1, 2, 3, 4, 5]
[2] LazyLock 访问
[2] LazyLock data = [1, 2, 3, 4, 5]
[3] LazyLock 访问
[3] LazyLock data = [1, 2, 3, 4, 5]
[1] LazyLock 访问
[1] LazyLock data = [1, 2, 3, 4, 5]
PS E:\leaning\slint\test>
