跳过正文

once_cell

·
目录
rust crate - 这篇文章属于一个选集。
§ 15: 本文

once_cell 是 lazy_static 的替代品,功能更加强大:支持局部 static 变量的初始化。

⚠️:Rust 1.70.0 开始支持 std::cell::OnceCell, std::sync::OnceLock, std::sync::LazyLock,可以用于替换 once_cell crate。


once_cell 提供了两个类型 unsync::OnceCellsync::OnceCell,可以用来保存 non-Copy 类型对象:

  • 对比: 标准库的 Cell 只能保存实现 Copy 的对象,而 RefCell 可以保存 non-Copy 对象
  • OnceCell 只能设置 set() 一次,后续的 get() 返回的是已保存的值;《— 单例模式
  • OnceCell 具有内部可变性,对于 set() 方法,使用共享引用。
  • sync::OnceCell 类型支持线程安全;
impl OnceCell<T> {
    fn new() -> OnceCell<T> { ... }
    fn set(&self, value: T) -> Result<(), T> { ... }
    fn get(&self) -> Option<&T> { ... }
}

// 示例:
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());
}

OnceCell 类型和标准库类型对比:

!Sync types Access Mode Drawbacks
Cell T requires T: Copy for get
RefCell RefMut / Ref may panic at runtime
unsync::OnceCell &T assignable only once
Sync types Access Mode Drawbacks
AtomicT T works only with certain Copy types
Mutex MutexGuard may deadlock at runtime, may block the thread
sync::OnceCell &T assignable only once, may block the thread
  1. 全局对象安全初始化:
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();
    // use `Logger::global()` from now on
}
  1. 全局对象延迟初始化
  • lazy_static!() 类似,但是没有使用 macro 语法和实现。
  • 可以使用 sync::Lazyunsync::Lazy 类型来简化代码(变量类型必须是 static 而非 const),支持线程安全。
use std::{sync::Mutex, collections::HashMap};

use once_cell::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)
    })
}

// 使用 Lazy 类型来简化代码
use std::{sync::Mutex, collections::HashMap};
use once_cell::sync::Lazy;

// 使用 Lazy 类型定义全局 static 变量
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 还支持局部变量
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);
}

// 如果是在 struct field 中,则使用 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())
}
}

使用 OnceCell 定义一个延迟初始化的正则表达式:返回一个 &'static Regex 类型的已编译、单例模式的 Regex 对象;

macro_rules! regex {
    ($re:literal $(,)?) => {{
        static RE: once_cell::sync::OnceCell<regex::Regex> = once_cell::sync::OnceCell::new();
        RE.get_or_init(|| regex::Regex::new($re).unwrap())
    }};
}

OnceCell 类型
#

线程安全的 cell 类型,只能被写入一次。(Rust 1.70.0 提供了类似的类型 std::sync::OnceLock)

use once_cell::sync::OnceCell;

static CELL: OnceCell<String> = OnceCell::new();
assert!(CELL.get().is_none());

std::thread::spawn(|| {
    let value: &String = CELL.get_or_init(|| {
        "Hello, World!".to_string()
    });
    assert_eq!(value, "Hello, World!");
}).join().unwrap();

let value: Option<&String> = CELL.get();
assert!(value.is_some());
assert_eq!(value.unwrap().as_str(), "Hello, World!");

OnceCell 的方法:

  • new() :创建;
  • set()/get_or_init() :设置值;
  • get()/get_mut(): 获取值;
pub const fn new() -> OnceCell<T>
pub const fn with_value(value: T) -> OnceCell<T>

// Gets the reference to the underlying value. Returns None if the cell is empty, or being
// initialized. This method never blocks.
// get 有可能返回 None,如 cell 没有初始化,或者正在初始化中时。
pub fn get(&self) -> Option<&T>

// 等待,直到 cell 被初始化完成
pub fn wait(&self) -> &T

pub fn get_mut(&mut self) -> Option<&mut T>
pub unsafe fn get_unchecked(&self) -> &T

// 当 cell 为 empty 时,返回 Ok,否则返回 Err(value)
pub fn set(&self, value: T) -> Result<(), T>
pub fn try_insert(&self, value: T) -> Result<&T, (&T, T)>

// Gets the contents of the cell, initializing it with f if the cell was empty.  Many threads may
// call get_or_init concurrently with different initializing functions, but it is guaranteed that
// only one function will be executed.
pub fn get_or_init<F>(&self, f: F) -> &T where F: FnOnce() -> T,

pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E> where F: FnOnce() -> Result<T, E>,
pub fn take(&mut self) -> Option<T>
pub fn into_inner(self) -> Option<T>

例子:

use once_cell::sync::OnceCell;

let mut cell = std::sync::Arc::new(OnceCell::new());
let t = std::thread::spawn({
    let cell = std::sync::Arc::clone(&cell);
    move || cell.set(92).unwrap()
});

// Returns immediately, but might return None.
let _value_or_none = cell.get();

// Will return 92, but might block until the other thread does `.set`.
let value: &u32 = cell.wait();
assert_eq!(*value, 92);


// set 方法
use once_cell::sync::OnceCell;
static CELL: OnceCell<i32> = OnceCell::new();

fn main() {
    assert!(CELL.get().is_none());

    std::thread::spawn(|| {
        assert_eq!(CELL.set(92), Ok(()));
    }).join().unwrap();

    assert_eq!(CELL.set(62), Err(62));
    assert_eq!(CELL.get(), Some(&92));
}

Lazy
#

OnceCell 需要明确的调用 set()/get_or_init() 方法来设置值。而 Lazy 则通过实现了 Deref/DerefMut trait,来在第一次 get() 或解引用访问时自动执行闭包函数来初始化值(Rust 1.70.0 开始支持提供了类似的类型 std::cell::LazyLock)。

  • Lazy 支持并发安全;
use std::collections::HashMap;

use once_cell::sync::Lazy;

static HASHMAP: Lazy<HashMap<i32, String>> = Lazy::new(|| {
    println!("initializing");
    let mut m = HashMap::new();
    m.insert(13, "Spica".to_string());
    m.insert(74, "Hoyten".to_string());
    m
});

fn main() {
    println!("ready");
    std::thread::spawn(|| {
        println!("{:?}", HASHMAP.get(&13)); // 第一个 get() 访问时,调用 Lazy::new() 闭包函数来设置值
    }).join().unwrap();
    println!("{:?}", HASHMAP.get(&74));

    // Prints:
    //   ready
    //   initializing
    //   Some("Spica")
    //   Some("Hoyten")
}
rust crate - 这篇文章属于一个选集。
§ 15: 本文

相关文章

axum
·
axum 是基于 hyper 实现的高性能异步 HTTP 1/2 Server 库。
clap
·
clap 用于快速构建命令行程序,提供命令&参数定义、解析等功能。
config
·
config 提供从文件或环境变量解析配置参数的功能。
diesel
·
diesel 是高性能的 ORM 和 Query Builder,crates.io 使用它来操作数据库。