Rust 1.70新特性:OnceCell和OnceLock

449次阅读  |  发布于1年以前

Rust 1.70新增了两个类型:OnceCell和OnceLock,它们都用于一次性初始化共享数据。其中OnceLock是OnceCell的线程安全版本。

OnceCell

只能写入一次的cell,它无需复制或替换就能获得对其内部值的共享&T引用(不像Cell),也没有运行时借用检查(不像RefCell)。但是,只能获得不可变引用,除非是cell本身的可变引用。

use std::cell::OnceCell;

fn main() {
    let cell = OnceCell::new();
    assert!(cell.get().is_none());

    let value: &String = cell.get_or_init(|| {
        "Hello, World!".to_string()
    });
    println!("{}", value);

    // 修改cell内的值
    let value1 = cell.set("value".to_string());
    println!("{:?}", value1);

    println!("{:?}", cell.get());
}

执行结果:

Hello, World!
Err("value")
Some("Hello, World!")

在这个例子中,我们在给cell赋值“Hello, World”后,再次修改cell的值为“value”。执行结果会打印出错误,cell的值没有发生改变。

在这里的get_or_init函数,可以获取OnceCell的内容,如果为空,则使用闭包对其进行初始化。如果闭包发生恐慌,则将恐慌传播给调用方,并且cell保持未初始化状态。

use std::cell::OnceCell;

fn main() {
    let cell = OnceCell::new();
    // let value = cell.get_or_init(|| 92);
    // assert_eq!(value, &92);
    let value = cell.get_or_init(|| unreachable!());
    assert_eq!(value, &92);
}

上面的代码编译不通过,因为value的值是unknow的。

OnceLock

只能写入一次的同步原语,这种类型是线程安全的OnceCell,可以在静态中使用。

use std::sync::OnceLock;

static WINNER: OnceLock<&str> = OnceLock::new();

fn main() {
    let winner = std::thread::scope(|s| {
        s.spawn(|| WINNER.set("thread"));

        std::thread::yield_now(); // give them a chance...

        WINNER.get_or_init(|| "main")
    });

    println!("{winner} wins!");
}

执行结果有可能是:"main wins!",也有可能是"thread wins!"。

lazy_static和once_cell等crate在过去已经满足了这一需求,但现在这些功能成为了标准库的一部分,是从once_cell的unsync和sync模块移植过来的。将来可能还会有更多的方法被稳定下来,例如LazyCell和LazyLock类型,它们是延迟初始化函数。


Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8