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