< 返回版块

黑豆腐 发表于 2020-12-11 17:23

Tags:rust

once_cell提供了unsync::OnceCellsync::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

评论区

写评论
awsa2ron 2021-07-04 11:58

最近在学习rust和设计模式,感觉单例模式singalton可以用这个once_cell实现。 单例模式确保一个类只有一个实例,并提供一个全局访问点。

单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 -- from wiki

once_cell的依赖有lazy_static,好处就是把lazy_static包裹了一层,然后对外提供非宏的方式。tokio就是基于这个理由换掉了lazy_static。 once_cell还依赖标准库的once,看once的实现原理简单的说就是用take只能拿走一次,那第二次就错误的方式,实现了once的语义。

whfuyn 2020-12-11 18:50

赞!

1 共 2 条评论, 1 页