Rust中创建全局的、可变单例的6种方法


一般来说,避免全局状态。
取而代之的是提早创建对象(甚至在main中首先创建对象),然后将该对象的可变引用传递到需要它的地方。
这通常会使你的代码更容易推理。

在决定你要使用全局可变变量之前,请仔细看看镜子里的自己。
在极少数情况下,它是有用的。

思路:可以用一个RwLock以允许多个并发的读者。

1、使用lazy-static
lazy -static crate 可以消除一些手动创建单例的苦差事。这是一个全局可变向量:

use lazy_static::lazy_static; // 1.4.0
use std::sync::Mutex;

lazy_static! {
    static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec!);
}

fn do_a_call() {
    ARRAY.lock().unwrap().push(1);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!(
"called {}", ARRAY.lock().unwrap().len());
}

2、使用once_cell
once_cell crate 可以消除一些手动创建单例的苦差事。这是一个全局可变向量:

use once_cell::sync::Lazy; // 1.3.1
use std::sync::Mutex;

static ARRAY: Lazy<Mutex<Vec<u8>>> = Lazy::new(|| Mutex::new(vec!));

fn do_a_call() {
    ARRAY.lock().unwrap().push(1);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!("called {}", ARRAY.lock().unwrap().len());
}

3、使用std::sync::LazyLock
标准库正在增加 once_cell 的功能,目前称为 LazyLock

#![feature(once_cell)] // 1.67.0-nightly
use std::sync::{LazyLock, Mutex};

static ARRAY: LazyLock<Mutex<Vec<u8>>> = LazyLock::new(|| Mutex::new(vec!));

fn do_a_call() {
    ARRAY.lock().unwrap().push(1);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!(
"called {}", ARRAY.lock().unwrap().len());
}

4、使用std::sync::OnceLock
LazyLock仍然不稳定,但OnceLock从Rust 1.70.0开始已经稳定了。你可以用它来获得稳定版的无依赖性实现:

use std::sync::{OnceLock, Mutex};

fn array() -> &'static Mutex<Vec<u8>> {
    static ARRAY: OnceLock<Mutex<Vec<u8>>> = OnceLock::new();
    ARRAY.get_or_init(|| Mutex::new(vec!))
}

fn do_a_call() {
    array().lock().unwrap().push(1);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!("called {}", array().lock().unwrap().len());
}

5、一个特例:Atomic
如果你只需要跟踪一个整数值,你可以直接使用atomic

use std::sync::atomic::{AtomicUsize, Ordering};

static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);

fn do_a_call() {
    CALL_COUNT.fetch_add(1, Ordering::SeqCst);
}

fn main() {
    do_a_call();
    do_a_call();
    do_a_call();

    println!("called {}", CALL_COUNT.load(Ordering::SeqCst));
}

6、手动、无依赖的实现
现有几种静态的实现,例如stdin的Rust 1.0实现。这也是适应现代Rust的想法,比如使用MaybeUninit来避免分配和不必要的间接性。你也应该看看io::Lazy的现代实现。在内联中注释了每一行的作用。

use std::sync::{Mutex, Once};
use std::time::Duration;
use std::{mem::MaybeUninit, thread};

struct SingletonReader {
    // Since we will be used in many threads, we need to protect
   
// concurrent access
    inner: Mutex<u8>,
}

fn singleton() -> &'static SingletonReader {
   
// Create an uninitialized static
    static mut SINGLETON: MaybeUninit<SingletonReader> = MaybeUninit::uninit();
    static ONCE: Once = Once::new();

    unsafe {
        ONCE.call_once(|| {
           
// Make it
            let singleton = SingletonReader {
                inner: Mutex::new(0),
            };
           
// Store it to the static var, i.e. initialize it
            SINGLETON.write(singleton);
        });

       
// Now we give out a shared reference to the data, which is safe to use
       
// concurrently.
        SINGLETON.assume_init_ref()
    }
}

fn main() {
   
// Let's use the singleton in a few threads
    let threads: Vec<_> = (0..10)
        .map(|i| {
            thread::spawn(move || {
                thread::sleep(Duration::from_millis(i * 10));
                let s = singleton();
                let mut data = s.inner.lock().unwrap();
                *data = i as u8;
            })
        })
        .collect();

   
// And let's check the singleton every so often
    for _ in 0u8..20 {
        thread::sleep(Duration::from_millis(5));

        let s = singleton();
        let data = s.inner.lock().unwrap();
        println!(
"It is: {}", *data);
    }

    for thread in threads.into_iter() {
        thread.join().unwrap();
    }
}

这段代码可以用Rust 1.55.0编译。

所有这些工作都是lazy-static或once_cell为你做的。

“全局 "的含义
请注意,你仍然可以使用正常的 Rust 范围和模块级隐私来控制对静态或 lazy_static 变量的访问。这意味着你可以在一个模块中,甚至在一个函数中声明它,而它在该模块/函数之外是不可访问的。这对于控制访问是很好的:

use lazy_static::lazy_static; // 1.2.0

fn only_here() {
    lazy_static! {
        static ref NAME: String = String::from(
"hello, world!");
    }
    
    println!(
"{}", &*NAME);
}

fn not_here() {
    println!(
"{}", &*NAME);
}
error[E0425]: cannot find value `NAME` in this scope
  --> src/lib.rs:12:22
   |
12 |     println!(
"{}", &*NAME);
   |                      ^^^^ not found in this scope

然而,这个变量仍然是全局性的,因为它在整个程序中只有一个实例存在。