1 个稳定版本
1.17.1 | 2023 年 3 月 14 日 |
---|
2491 在 Rust 模式
5,943 每月下载量
用于 kodept-ast
89KB
1K SLoC
该项目是 https://github.com/matklad/once_cell,在 https://github.com/matklad/once_cell/pull/104 中提出了对 serde 的支持。
lib.rs
:
概述
once_cell
提供了两种新的类似单元类型,unsync::OnceCell
和 sync::OnceCell
。一个 OnceCell
可能存储任意非 Copy
类型,最多只能赋值一次,并提供对存储内容的直接访问。核心 API 看起来大致如下(还有很多其他功能,请继续阅读!)
impl<T> OnceCell<T> {
const fn new() -> OnceCell<T> { ... }
fn set(&self, value: T) -> Result<(), T> { ... }
fn get(&self) -> Option<&T> { ... }
}
注意,与 RefCell
和 Mutex
类似,set
方法只需要一个共享引用。由于单次赋值限制,get
可以返回一个 &T
而不是 Ref<T>
或 MutexGuard<T>
。
sync
风格是线程安全的(即实现了 Sync
特性),而 unsync
风格则不是。
食谱
OnceCell
可能适用于各种模式。
全局数据的安全初始化
use std::{env, io};
use once_cell_serde::sync::OnceCell;
#[derive(Debug)]
pub struct Logger {
// ...
}
static INSTANCE: OnceCell<Logger> = OnceCell::new();
impl Logger {
pub fn global() -> &'static Logger {
INSTANCE.get().expect("logger is not initialized")
}
fn from_cli(args: env::Args) -> Result<Logger, std::io::Error> {
// ...
}
}
fn main() {
let logger = Logger::from_cli(env::args()).unwrap();
INSTANCE.set(logger).unwrap();
// use `Logger::global()` from now on
}
延迟初始化的全局数据
这本质上与 lazy_static!
宏相同,但没有宏。
use std::{sync::Mutex, collections::HashMap};
use once_cell_serde::sync::OnceCell;
fn global_data() -> &'static Mutex<HashMap<i32, String>> {
static INSTANCE: OnceCell<Mutex<HashMap<i32, String>>> = OnceCell::new();
INSTANCE.get_or_init(|| {
let mut m = HashMap::new();
m.insert(13, "Spica".to_string());
m.insert(74, "Hoyten".to_string());
Mutex::new(m)
})
}
此外,还有简化该模式的便捷类型 sync::Lazy
和 unsync::Lazy
use std::{sync::Mutex, collections::HashMap};
use once_cell_serde::sync::Lazy;
static GLOBAL_DATA: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| {
let mut m = HashMap::new();
m.insert(13, "Spica".to_string());
m.insert(74, "Hoyten".to_string());
Mutex::new(m)
});
fn main() {
println!("{:?}", GLOBAL_DATA.lock().unwrap());
}
请注意,持有 Lazy
的变量声明为 static
,而不是 const
。这很重要:使用 const
会导致编译错误。
通用惰性求值
与 lazy_static!
不同,Lazy
可以与局部变量一起使用。
use once_cell_serde::unsync::Lazy;
fn main() {
let ctx = vec![1, 2, 3];
let thunk = Lazy::new(|| {
ctx.iter().sum::<i32>()
});
assert_eq!(*thunk, 6);
}
如果您需要在结构体中有一个惰性字段,您可能应该直接使用 OnceCell
,因为这将允许您在初始化期间访问 self
。
use std::{fs, path::PathBuf};
use once_cell_serde::unsync::OnceCell;
struct Ctx {
config_path: PathBuf,
config: OnceCell<String>,
}
impl Ctx {
pub fn get_config(&self) -> Result<&str, std::io::Error> {
let cfg = self.config.get_or_try_init(|| {
fs::read_to_string(&self.config_path)
})?;
Ok(cfg.as_str())
}
}
惰性编译的正则表达式
这是一个 regex!
宏,它接受一个字符串字面量并返回一个 表达式,该表达式求值为一个 &'static Regex
macro_rules! regex {
($re:literal $(,)?) => {{
static RE: once_cell_serde::sync::OnceCell<regex::Regex> = once_cell_serde::sync::OnceCell::new();
RE.get_or_init(|| regex::Regex::new($re).unwrap())
}};
}
此宏可以避免每次循环迭代都编译正则表达式的问题。
运行时 include_bytes!
该 include_bytes
宏用于包含测试资源,但它会大大减慢测试编译速度。一种替代方法是运行时加载资源
use std::path::Path;
use once_cell_serde::sync::OnceCell;
pub struct TestResource {
path: &'static str,
cell: OnceCell<Vec<u8>>,
}
impl TestResource {
pub const fn new(path: &'static str) -> TestResource {
TestResource { path, cell: OnceCell::new() }
}
pub fn bytes(&self) -> &[u8] {
self.cell.get_or_init(|| {
let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let path = Path::new(dir.as_str()).join(self.path);
std::fs::read(&path).unwrap_or_else(|_err| {
panic!("failed to load test resource: {}", path.display())
})
}).as_slice()
}
}
static TEST_IMAGE: TestResource = TestResource::new("test_data/lena.png");
#[test]
fn test_sobel_filter() {
let rgb: &[u8] = TEST_IMAGE.bytes();
// ...
}
lateinit
LateInit
类型用于延迟初始化。它类似于 Kotlin 的 lateinit
关键字,并允许构建循环数据结构
use once_cell_serde::sync::OnceCell;
pub struct LateInit<T> { cell: OnceCell<T> }
impl<T> LateInit<T> {
pub fn init(&self, value: T) {
assert!(self.cell.set(value).is_ok())
}
}
impl<T> Default for LateInit<T> {
fn default() -> Self { LateInit { cell: OnceCell::default() } }
}
impl<T> std::ops::Deref for LateInit<T> {
type Target = T;
fn deref(&self) -> &T {
self.cell.get().unwrap()
}
}
#[derive(Default)]
struct A<'a> {
b: LateInit<&'a B<'a>>,
}
#[derive(Default)]
struct B<'a> {
a: LateInit<&'a A<'a>>
}
fn build_cycle() {
let a = A::default();
let b = B::default();
a.b.init(&b);
b.a.init(&a);
let _a = &a.b.a.b.a;
}
与 std 的比较
!Sync 类型 |
访问模式 | 缺点 |
---|---|---|
Cell<T> |
T |
对于 get 需要 T: Copy |
RefCell<T> |
RefMut<T> / Ref<T> |
可能在运行时引发恐慌 |
unsync::OnceCell<T> |
&T |
只能分配一次 |
Sync 类型 |
访问模式 | 缺点 |
---|---|---|
AtomicT |
T |
仅与某些 Copy 类型一起工作 |
Mutex<T> |
MutexGuard<T> |
可能在运行时发生死锁,可能阻塞线程 |
sync::OnceCell<T> |
&T |
只能分配一次,可能阻塞线程 |
技术上,调用 get_or_init
也会导致恐慌或死锁,如果它递归调用自己。然而,由于赋值只能发生一次,此类情况应比使用 RefCell
和 Mutex
的等效情况更少。
最低支持的 rustc
版本
此包最低支持的 rustc
版本是 1.56.0
。
如果仅启用 std
功能,MSRV 将保守更新,支持至少编译器的最新 8 个版本。当使用其他功能,如 parking_lot
时,MSRV 可能会更新得更频繁,直至最新稳定版本。在两种情况下,增加 MSRV 都不被视为 semver 破坏性更改。
实现细节
实现基于 lazy_static
和 lazy_cell
库以及 std::sync::Once
。在某种程度上,once_cell
只是简化并统一了这些 API。
为了实现 OnceCell
的同步版本,此库使用自定义的 std::sync::Once
或 parking_lot::Mutex
重实现。这由 parking_lot
功能控制(默认禁用)。两种情况下的性能相同,但基于 parking_lot
的 OnceCell<T>
更小,最多可达 16 字节。
此库使用 unsafe
。
常见问题解答。
我应该使用 lazy_static 还是 once_cell?
从第一近似来看,once_cell
既有更多的灵活性,又更方便,应该优先选择。
与 once_cell
不同,lazy_static
支持基于自旋锁的阻塞实现,这与 #![no_std]
一起工作。
lazy_static
已经接受了大量的实际测试,但 once_cell
也是一个广泛使用的库。
我应该使用同步还是非同步版本?
因为 Rust 编译器会为你检查线程安全,所以不可能意外地在使用 sync
时使用 unsync
。因此,在单线程代码中使用 unsync
,在多线程代码中使用 sync
。如果代码后来变为多线程,很容易在这两种之间切换。
目前,unsync
有一个额外的优点,即重入初始化会导致 panic,这比死锁更容易调试。
这个库支持异步操作吗?
不支持,但你可以使用 async_once_cell
。
我能自带互斥锁吗?
有 generic_once_cell 允许这样做。
相关库
- double-checked-cell
- lazy-init
- lazycell
- mitochondria
- lazy_static
- async_once_cell
- generic_once_cell (自带互斥锁)
此库的大部分功能在 nightly Rust 中的 std
中可用。请参阅 跟踪问题。
依赖项
~0–5MB