如何创建一个全局的,可变的单例?

时间:2022-10-30 09:14:35

What is the best way to create and use a struct with only one instantiation in the system? Yes, this is necessary, it is the OpenGL subsystem, and making multiple copies of this and passing it around everywhere would add confusion, rather than relieve it.

在系统中只创建一个实例的结构创建和使用的最佳方法是什么?是的,这是必要的,它是OpenGL子系统,制作多个副本并将其传递到各处会增加混乱,而不是减轻它。

The singleton needs to be as efficient as possible. It doesn't seem possible to store an arbitrary object on the static area, as it contains a Vec with a destructor. The second option is to store an (unsafe) pointer on the static area, pointing to a heap allocated singleton. What is the most convenient and safest way to do this, while keeping syntax terse.

单身人士需要尽可能高效。似乎不可能在静态区域上存储任意对象,因为它包含带有析构函数的Vec。第二个选项是在静态区域存储(不安全)指针,指向堆分配的单例。什么是最方便和最安全的方法,同时保持语法简洁。

1 个解决方案

#1


Non-answer answer

Avoid global state in general. Instead, construct the object somewhere early (perhaps in main), then pass mutable references to that object into the places that need it. This will usually make your code easier to reason about and doesn't require as much bending over backwards.

一般避免全球状态。相反,尽早在某处构建对象(可能在main中),然后将对该对象的可变引用传递到需要它的位置。这通常会使您的代码更易于推理,并且不需要向后弯曲。

Look hard at yourself in the mirror before deciding that you want global mutable variables. There are rare cases where it's useful, so that's why it's worth knowing how to do.

在决定你想要全局可变变量之前,先在镜子里仔细看看。在极少数情况下它很有用,所以这就是为什么它值得知道怎么做。

Still want to make one...?

还想做一个......?

Using lazy-static

The lazy-static crate can take away some of the drudgery of creating a singleton (below). Here is a global mutable vector:

懒惰的静态箱子可以带走一些创造单身人士的苦差事(下图)。这是一个全局可变向量:

#[macro_use]extern crate lazy_static;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());}

If you remove the Mutex then you have a global singleton without any mutability.

如果删除Mutex,那么您将拥有一个没有任何可变性的全局单例。

A special case: atomics

If you only need to track an integer value, you can directly use an 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));}

Manual, dependency-free implementation

This is greatly cribbed from the Rust 1.0 implementation of stdin. You should also look at the modern implementation of io::Lazy. I've commented inline with what each line does.

这与stdin的Rust 1.0实现有很大关系。您还应该看一下io :: Lazy的现代实现。我已经评论了每行的内容。

use std::sync::{Arc, Mutex, Once, ONCE_INIT};use std::time::Duration;use std::{mem, thread};#[derive(Clone)]struct SingletonReader {    // Since we will be used in many threads, we need to protect    // concurrent access    inner: Arc<Mutex<u8>>,}fn singleton() -> SingletonReader {    // Initialize it to a null value    static mut SINGLETON: *const SingletonReader = 0 as *const SingletonReader;    static ONCE: Once = ONCE_INIT;    unsafe {        ONCE.call_once(|| {            // Make it            let singleton = SingletonReader {                inner: Arc::new(Mutex::new(0)),            };            // Put it in the heap so it can outlive this call            SINGLETON = mem::transmute(Box::new(singleton));        });        // Now we give out a copy of the data that is safe to use concurrently.        (*SINGLETON).clone()    }}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();    }}

This prints out:

打印出:

It is: 0It is: 1It is: 1It is: 2It is: 2It is: 3It is: 3It is: 4It is: 4It is: 5It is: 5It is: 6It is: 6It is: 7It is: 7It is: 8It is: 8It is: 9It is: 9It is: 9

This code compiles with Rust 1.23.0. The real implementations of Stdin use some unstable features to attempt to free the allocated memory, which this code does not.

此代码使用Rust 1.23.0编译。 Stdin的实际实现使用一些不稳定的特性来尝试释放分配的内存,而这些代码没有。

Really, you'd probably want to make SingletonReader implement Deref and DerefMut so you didn't have to poke into the object and lock it yourself.

实际上,你可能想让SingletonReader实现Deref和DerefMut,这样你就不必戳入对象并自己锁定它。

All of this work is what lazy-static does for you.

所有这些工作都是lazy-static为您所做的。

The meaning of "global"

Please note that you can still use normal Rust scoping and module-level privacy to control access to a static or lazy_static variable. This means that you can declare it in a module or even inside of a function and it won't be accessible outside of that module / function. This is good for controlling access:

请注意,您仍然可以使用正常的Rust范围和模块级隐私来控制对static或lazy_static变量的访问。这意味着您可以在模块中或甚至在函数内部声明它,并且它将无法在该模块/函数之外访问。这有利于控制访问:

use lazy_static::lazy_static; // 1.2.0fn 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

However, the variable is still global in that there's one instance of it that exists across the entire program.

但是,变量仍然是全局变量,因为整个程序中存在一个变量实例。

#1


Non-answer answer

Avoid global state in general. Instead, construct the object somewhere early (perhaps in main), then pass mutable references to that object into the places that need it. This will usually make your code easier to reason about and doesn't require as much bending over backwards.

一般避免全球状态。相反,尽早在某处构建对象(可能在main中),然后将对该对象的可变引用传递到需要它的位置。这通常会使您的代码更易于推理,并且不需要向后弯曲。

Look hard at yourself in the mirror before deciding that you want global mutable variables. There are rare cases where it's useful, so that's why it's worth knowing how to do.

在决定你想要全局可变变量之前,先在镜子里仔细看看。在极少数情况下它很有用,所以这就是为什么它值得知道怎么做。

Still want to make one...?

还想做一个......?

Using lazy-static

The lazy-static crate can take away some of the drudgery of creating a singleton (below). Here is a global mutable vector:

懒惰的静态箱子可以带走一些创造单身人士的苦差事(下图)。这是一个全局可变向量:

#[macro_use]extern crate lazy_static;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());}

If you remove the Mutex then you have a global singleton without any mutability.

如果删除Mutex,那么您将拥有一个没有任何可变性的全局单例。

A special case: atomics

If you only need to track an integer value, you can directly use an 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));}

Manual, dependency-free implementation

This is greatly cribbed from the Rust 1.0 implementation of stdin. You should also look at the modern implementation of io::Lazy. I've commented inline with what each line does.

这与stdin的Rust 1.0实现有很大关系。您还应该看一下io :: Lazy的现代实现。我已经评论了每行的内容。

use std::sync::{Arc, Mutex, Once, ONCE_INIT};use std::time::Duration;use std::{mem, thread};#[derive(Clone)]struct SingletonReader {    // Since we will be used in many threads, we need to protect    // concurrent access    inner: Arc<Mutex<u8>>,}fn singleton() -> SingletonReader {    // Initialize it to a null value    static mut SINGLETON: *const SingletonReader = 0 as *const SingletonReader;    static ONCE: Once = ONCE_INIT;    unsafe {        ONCE.call_once(|| {            // Make it            let singleton = SingletonReader {                inner: Arc::new(Mutex::new(0)),            };            // Put it in the heap so it can outlive this call            SINGLETON = mem::transmute(Box::new(singleton));        });        // Now we give out a copy of the data that is safe to use concurrently.        (*SINGLETON).clone()    }}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();    }}

This prints out:

打印出:

It is: 0It is: 1It is: 1It is: 2It is: 2It is: 3It is: 3It is: 4It is: 4It is: 5It is: 5It is: 6It is: 6It is: 7It is: 7It is: 8It is: 8It is: 9It is: 9It is: 9

This code compiles with Rust 1.23.0. The real implementations of Stdin use some unstable features to attempt to free the allocated memory, which this code does not.

此代码使用Rust 1.23.0编译。 Stdin的实际实现使用一些不稳定的特性来尝试释放分配的内存,而这些代码没有。

Really, you'd probably want to make SingletonReader implement Deref and DerefMut so you didn't have to poke into the object and lock it yourself.

实际上,你可能想让SingletonReader实现Deref和DerefMut,这样你就不必戳入对象并自己锁定它。

All of this work is what lazy-static does for you.

所有这些工作都是lazy-static为您所做的。

The meaning of "global"

Please note that you can still use normal Rust scoping and module-level privacy to control access to a static or lazy_static variable. This means that you can declare it in a module or even inside of a function and it won't be accessible outside of that module / function. This is good for controlling access:

请注意,您仍然可以使用正常的Rust范围和模块级隐私来控制对static或lazy_static变量的访问。这意味着您可以在模块中或甚至在函数内部声明它,并且它将无法在该模块/函数之外访问。这有利于控制访问:

use lazy_static::lazy_static; // 1.2.0fn 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

However, the variable is still global in that there's one instance of it that exists across the entire program.

但是,变量仍然是全局变量,因为整个程序中存在一个变量实例。