once_cell
提供了unsync::OnceCell
和sync::OnceCell
这两种Cell(字面意思,前者用于单线程,后者用于多线程),用来存储堆上的信息,并且具有最多只能赋值一次的特性。 API大概是:
impl<T> OnceCell<T> {
fn new() -> OnceCell<T> { ... }
fn set(&self, value: T) -> Result<(), T> { ... }
fn get(&self) -> Option<&T> { ... }
}
用法
安全的初始化全局变量
use std::{env, io};
use once_cell::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();
// 之后就统一使用`Logger::global()`
}
Lazy产生全局变量
包中提供了Lazy
,(也分sync和unsync版)可以起到类似之前提到过的lazy_static的作用——在第一次使用的时候产生一个动态的全局静态变量
use std::{sync::Mutex, collections::HashMap};
use once_cell::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());
}
其中的好处就是不需要神奇的宏魔法了,这也是近期tokio
使用它取代了之前的lazy_static
的原因:https://github.com/tokio-rs/tokio/pull/3187
比起lazy_static
,我们还支持局部变量
use once_cell::unsync::Lazy;
fn main() {
let ctx = vec![1, 2, 3];
let thunk = Lazy::new(|| {
ctx.iter().sum::<i32>()
});
assert_eq!(*thunk, 6);
}
而且使用OnceCell还可以嵌入到结构体中
use std::{fs, path::PathBuf};
use once_cell::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())
}
}
因此可以用它来构建其它更复杂的东西!
小结
那大家可能好奇它和lazy_static
究竟有什么区别。小结一下就是:
- 就像上边的例子展现的,
once_cell
更灵活易用,也没用宏魔法 lazy_static
可以支持no_std
不过Lazy
往上游提交pr也有很长时间了,有兴趣的可以去围观下https://github.com/rust-lang/rfcs/pull/2788
1
共 2 条评论, 1 页
评论区
写评论最近在学习rust和设计模式,感觉单例模式singalton可以用这个once_cell实现。 单例模式确保一个类只有一个实例,并提供一个全局访问点。
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 -- from wiki
once_cell的依赖有lazy_static,好处就是把lazy_static包裹了一层,然后对外提供非宏的方式。tokio就是基于这个理由换掉了lazy_static。 once_cell还依赖标准库的once,看once的实现原理简单的说就是用take只能拿走一次,那第二次就错误的方式,实现了once的语义。
赞!