一般来说,避免全局状态。
取而代之的是提早创建对象(甚至在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
|
然而,这个变量仍然是全局性的,因为它在整个程序中只有一个实例存在。