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...?
还想做一个吗...?
Tips
In the following solutions:
在以下解决方案中:
- If you remove the
Mutex
then you have a global singleton without any mutability.
- You can also use a
RwLock
instead of a Mutex
to allow multiple concurrent readers.
Using lazy-static
The lazy-static crate can take away some of the drudgery of manually creating a singleton. Here is a global mutable vector:
懒惰的静态板条箱可以省去一些手动创建单件对象的繁琐工作。下面是一个全局可变向量:
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());
}
Using once_cell
The once_cell crate can take away some of the drudgery of manually creating a singleton. Here is a global mutable vector:
Once_cell板条箱可以省去一些手工创建单例的繁琐工作。下面是一个全局可变向量:
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());
}
Using std::sync::LazyLock
The standard library is in the process of adding once_cell
's functionality, currently called 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());
}
Using std::sync::OnceLock
LazyLock
is still unstable, but OnceLock
was stabilized as of Rust 1.70.0. You can use it to get dependency-free implementation on stable:
LazyLock仍然不稳定,但OnceLock在Rust 1.70.0版本中已经稳定下来。您可以使用它在STRISE上获得无依赖实现:
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());
}
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
There are several existing implementation of statics, such as the Rust 1.0 implementation of stdin
. This is the same idea adapted to modern Rust, such as the use of MaybeUninit
to avoid allocations and unnecessary indirection. You should also look at the modern implementation of io::Lazy
. I've commented inline with what each line does.
有几种现有的静态实现,例如标准输入的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();
}
}
This prints out:
这将打印出来:
It is: 0
It is: 1
It is: 1
It is: 2
It is: 2
It is: 3
It is: 3
It is: 4
It is: 4
It is: 5
It is: 5
It is: 6
It is: 6
It is: 7
It is: 7
It is: 8
It is: 8
It is: 9
It is: 9
It is: 9
This code compiles with Rust 1.55.0.
此代码使用Rust 1.55.0进行编译。
All of this work is what lazy-static or once_cell do for you.
所有这些工作都是Lazy-Static或Once_cell为您做的。
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作用域和模块级别的隐私来控制对静态变量或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
However, the variable is still global in that there's one instance of it that exists across the entire program.
但是,该变量仍然是全局的,因为它的一个实例存在于整个程序中。
Starting with Rust 1.63, it can be easier to work with global mutable singletons, although it's still preferable to avoid global variables in most cases.
从Rust 1.63开始,可以更容易地使用全局可变单例,尽管在大多数情况下避免全局变量仍然是可取的。
Now that Mutex::new
is const
, you can use global static Mutex
locks without needing lazy initialization:
既然Mutex::New是常量,您就可以使用全局静态Mutex锁,而无需延迟初始化:
use std::sync::Mutex;
static GLOBAL_DATA: Mutex<Vec<i32>> = Mutex::new(Vec::new());
fn main() {
GLOBAL_DATA.lock().unwrap().push(42);
println!("{:?}", GLOBAL_DATA.lock().unwrap());
}
Note that this also depends on the fact that Vec::new
is const
. If you need to use non-const
functions to set up your singleton, you could wrap your data in an Option
, and initially set it to None
. This lets you use data structures like Hashset
which currently cannot be used in a const
context:
请注意,这还取决于VEC::New是常量这一事实。如果您需要使用非常量函数来设置Singleton,您可以将数据包装在一个选项中,并将其初始设置为None。这允许您使用当前不能在常量上下文中使用的数据结构,如Hashset:
use std::sync::Mutex;
use std::collections::HashSet;
static GLOBAL_DATA: Mutex<Option<HashSet<i32>>> = Mutex::new(None);
fn main() {
*GLOBAL_DATA.lock().unwrap() = Some(HashSet::from([42]));
println!("V2: {:?}", GLOBAL_DATA.lock().unwrap());
}
Alternatively, you could use an RwLock, instead of a Mutex
, since RwLock::new
is also const
as of Rust 1.63. This would make it possible to read the data from multiple threads simultaneously.
或者,您也可以使用RavLock,而不是Mutex,因为Rust 1.63版本中的RavLock::New也是常量。这将使从多个线程同时读取数据成为可能。
If you need to initialize with non-const
functions and you'd prefer not to use an Option
, you could use a crate like once_cell or lazy-static for lazy initialization as explained in Shepmaster's answer.
如果您需要使用非常数函数进行初始化,并且您不希望使用选项,则可以使用像once_cell或lazy-static这样的板条箱来进行延迟初始化,如谢普马斯特的回答所解释的那样。
From What Not To Do In Rust
在Rust中不能做的事情
To recap: instead of using interior mutability where an object changes
its internal state, consider using a pattern where you promote new
state to be current and current consumers of the old state will
continue to hold on to it by putting an Arc into an RwLock.
use std::sync::{Arc, RwLock};
#[derive(Default)]
struct Config {
pub debug_mode: bool,
}
impl Config {
pub fn current() -> Arc<Config> {
CURRENT_CONFIG.with(|c| c.read().unwrap().clone())
}
pub fn make_current(self) {
CURRENT_CONFIG.with(|c| *c.write().unwrap() = Arc::new(self))
}
}
thread_local! {
static CURRENT_CONFIG: RwLock<Arc<Config>> = RwLock::new(Default::default());
}
fn main() {
Config { debug_mode: true }.make_current();
if Config::current().debug_mode {
// do something
}
}
If you are on nightly, you can use LazyLock
.
如果你是晚上开机,你可以使用LazyLock。
It more or less does what the crates once_cell and lazy_sync do. Those two crates are very common, so there's a good chance they might already by in your Cargo.lock
dependency tree. But if you prefer to be a bit more "adventurous" and go with LazyLock
, be prepered that it (as everything in nightly) might be a subject to change before it gets to stable.
它或多或少完成了板条箱Once_cell和lazy_sync所做的工作。这两个箱子非常常见,因此它们很可能已经存在于您的Cargo.lock依赖关系树中。但是,如果你更喜欢“冒险”一点,选择LazyLock,那么请注意,在它稳定之前,它(就像晚上的一切一样)可能会发生变化。
(Note: Up until recently std::sync::LazyLock
used to be named std::lazy::SyncLazy
but was recently renamed.)
(注意:直到最近,Std::Sync::LazyLock还被命名为Std::Lazy::SyncLazy,但最近被重新命名。)
Use SpinLock for global access.
使用自旋锁进行全局访问。
#[derive(Default)]
struct ThreadRegistry {
pub enabled_for_new_threads: bool,
threads: Option<HashMap<u32, *const Tls>>,
}
impl ThreadRegistry {
fn threads(&mut self) -> &mut HashMap<u32, *const Tls> {
self.threads.get_or_insert_with(HashMap::new)
}
}
static THREAD_REGISTRY: SpinLock<ThreadRegistry> = SpinLock::new(Default::default());
fn func_1() {
let thread_registry = THREAD_REGISTRY.lock(); // Immutable access
if thread_registry.enabled_for_new_threads {
}
}
fn func_2() {
let mut thread_registry = THREAD_REGISTRY.lock(); // Mutable access
thread_registry.threads().insert(
// ...
);
}
If you want mutable state(NOT Singleton), see What Not to Do in Rust for more descriptions.
如果您想要可变状态(而不是Singleton),请参阅在Rust中不能做什么以获得更多描述。
Hope it's helpful.
希望能对你有所帮助。
A bit late to the party, but here's how I worked around this issue (rust 1.66-nightly):
参加派对有点晚了,但以下是我如何解决这个问题的方法(铁锈1.66-每晚):
#![feature(const_size_of_val)]
#![feature(const_ptr_write)]
static mut GLOBAL_LAZY_MUT: StructThatIsNotSyncNorSend = unsafe {
// Copied from MaybeUninit::zeroed() with minor modifications, see below
let mut u = MaybeUninit::uninit();
let bytes = mem::size_of_val(&u);
write_bytes(u.as_ptr() as *const u8 as *mut u8, 0xA5, bytes); //Trick the compiler check that verifies pointers and references are not null.
u.assume_init()
};
(...)
fn main() {
unsafe {
let mut v = StructThatIsNotSyncNorSend::new();
mem::swap(&mut GLOBAL_LAZY_MUT, &mut v);
mem::forget(v);
}
}
Beware that this code is unbelievably unsafe, and can easily end up being UB if not handled correctly.
请注意,此代码非常不安全,如果处理不当,很容易导致UB。
You now have a !Send !Sync value as a global static, without the protection of a Mutex. If you access it from multiple threads, even if just for reading, it's UB. If you don't initialize it the way shown, it's UB, because it calls Drop on an actually unitialized value.
现在,您拥有了一个!Send!Sync值作为全局静态变量,而不需要互斥体的保护。如果您从多个线程访问它,即使只是为了读取,它也是UB。如果您没有按照所示的方式对它进行初始化,那么它就是UB,因为它在一个实际的单元化值上调用Drop。
You just convinced the rust compiler that something that is UB is not UB. You just convinced that putting a !Sync and !Send in a global static is fine.
您刚刚说服了铁锈编译器,UB不是UB。您刚刚确信放入一个!Sync和!Send in一个全局静态变量是可以的。
If unsure, don't use this snippet.
如果不确定,请不要使用此代码段。
Besides the 3rd-party crates, the alternative is to wrap your custom type (e.g. struct) within std::sync::Mutex
.
除了第三方板条箱,另一种方法是将您的自定义类型(例如,struct)包装在std::sync::Mutex中。
- The
Mutex
protects the instance of the custom type in concurrent accesses of multi-threading usage scenario
- The caller successfully getting the lock from
Mutex
can modify the content of the custom type.
Here's the code example :
以下是代码示例:
use std::sync::Mutex;
#[derive(Debug)]
struct Rectangle {
width :u16,
height:u16,
}
static GLOBAL_COUNTER_2: Mutex<Rectangle> =
Mutex::new(
Rectangle{width:100u16, height:125u16}
);
fn global_var_demo()
{
if let Ok(mut value) = GLOBAL_COUNTER_2.lock() {
value.width += 7;
value.height = value.height >> 1;
}
if let Ok(value) = GLOBAL_COUNTER_2.lock() {
// request the reference without moving the ownership
assert_eq!(value.width, 107u16);
println!("new value in GLOBAL_COUNTER_2: {:?}", value);
}
}
My limited solution is to define a struct instead of a global mutable one. To use that struct, external code needs to call init() but we disallow calling init() more than once by using an AtomicBoolean (for multithreading usage).
我有限的解决方案是定义一个结构体,而不是一个全局可变结构体。要使用该结构体,外部代码需要调用init(),但我们不允许通过使用AtomicBoolean(用于多线程)多次调用init()。
static INITIATED: AtomicBool = AtomicBool::new(false);
struct Singleton {
...
}
impl Singleton {
pub fn init() -> Self {
if INITIATED.load(Ordering::Relaxed) {
panic!("Cannot initiate more than once")
} else {
INITIATED.store(true, Ordering::Relaxed);
Singleton {
...
}
}
}
}
fn main() {
let singleton = Singleton::init();
// panic here
// let another_one = Singleton::init();
...
}
更多回答
After a lot of thought I'm convinced not to use the Singleton, and instead use no global variables at all and pass everything around. Makes the code more self-documenting since it is clear what functions access the renderer. If I want to change back to singleton, it will be easier to do that than the other way around.
经过深思熟虑后,我确信不使用Singleton,而是完全不使用全局变量,并传递所有内容。使代码更具自我文档化,因为可以清楚地知道哪些函数可以访问呈现器。如果我想改回单身,这将比反过来更容易。
Thanks for the answer, it helped a lot. I just thought I'd let here a comment to describe what I see as a valid use case for lazy_static!. I am using it to interface to a C application that allows loading/unloading modules (shared objects) and the rust code is one of these modules. I don't see much option than using a global on load because I have no control over main() at all and how the core application interfaces with my module. I basically needed a vector of things that can be added on runtime after my mod is loaded.
谢谢你的回答,它帮了很大的忙。我只是想在这里添加一个注释来描述我所看到的LAZY_STATIC!的有效用例。我使用它来连接一个允许加载/卸载模块(共享对象)的C应用程序,Ruust代码就是这些模块之一。我看不到比使用全局加载更多的选择,因为我根本无法控制main()以及核心应用程序如何与我的模块交互。我基本上需要一个可以在我的mod加载后在运行时添加的东西的矢量。
Yeah passing around context would work, but this is a large application we don't really have much control over and changing the interface to modules would imply updating hundreds of third party modules or creating a new module API, both changes involve far more work than just writing a plugin module using lazy-static.
是的,传递上下文是可行的,但这是一个大型应用程序,我们并不能对其进行太多控制,将接口更改为模块将意味着更新数百个第三方模块或创建新的模块API,这两个更改都涉及比仅使用惰性静态编写插件模块多得多的工作。
Not a good answer. Avoid global state in general
but global state is a thing and need representation. And the code for external static
is flawed and will not compile on rustc 1.24.1
这不是一个好答案。总体上避免全局状态,但全局状态是一种东西,需要表现。外部静态代码有缺陷,不能在rustc 1.24.1上编译
@Worik would you care to explain why? I discourage people from doing something that is a poor idea in most languages (even the OP agreed that a global was a bad choice for their application). That's what in general means. I then show two solutions for how to do it anyway. I just tested the lazy_static
example in Rust 1.24.1 and it works exactly. There's no external static
anywhere here. Perhaps you need to check things on your end to make sure you've understood the answer fully.
@worik你能解释一下为什么吗?我不鼓励人们做一些在大多数语言中都是糟糕想法的事情(就连OP也同意,全局变量对于他们的应用程序来说是一个糟糕的选择)。这就是通常的意思。然后,我展示了两种解决方案来解决如何做到这一点。我刚刚在Rust 1.24.1中测试了lazy_Static示例,它完全正常工作。这里任何地方都没有外部静电。也许你需要检查一下你那一端的东西,以确保你已经完全理解了答案。
Hi, please see this question as I'm not sure the thread_local
is correct, as it will create multiple instances of the Arc<Config>
(one per running thread).
嗨,请看这个问题,因为我不确定THREAD_LOCAL是否正确,因为它将创建Arc的多个实例(每个运行的线程一个)。
Also, isn't it more or less pointless to use thread-safe data structures like RwLock
and Arc
inside a thread_local!
storage that cannot pass thread boundaries by design? Shouldn't the non-thread-safe versions RefCell
and Rc
be sufficient here?
此外,在THREAD_LOCAL中使用像RwLock和Arc这样的线程安全数据结构是不是或多或少毫无意义!在设计上不能越过线程边界的存储?难道非线程安全版本RefCell和rc在这里还不够吗?
Why is this not UB? You are creating an uninitialized value, which, AFAIK, is instantaneous UB even if you don't read it before properly initializing it. Also, you have an unsafe
block within an unsafe
block (maybe to point out it really unsafe?)
为什么这不是UB?您正在创建一个未初始化的值,即使您在正确初始化它之前没有读取它,AFAIK也是瞬时UB。另外,在不安全的块中有一个不安全的块(也许是为了指出它真的不安全?)
The nested unsafe is not necessary, that is a mistake. The code is not UB because the value is overwritten before it is read (the initialization needs to be in main, before any race conditions could happen). What you may be referring to (UB even it it's not read) happens if the value is overwritten, and the rust compiler tries to Drop the old (unitited) value. Since it's mem::swap
-ped and mem::forget
-ted here, that doesn't happen.
嵌套的不安全是不必要的,这是一个错误。代码不是UB,因为值在读取之前被覆盖(初始化需要在Main中,才能发生任何争用条件)。如果值被覆盖,并且RUST编译器尝试删除旧的(单元化的)值,则会发生您可能指的(UB,即使它没有被读取)。因为这里是mem::交换和mem::忘记-Ted,所以不会发生这种情况。
That may seem intuitively correct, and yet, even having an unitialized variable, even if you never access it, is UB, I think. See the doc, in particular the example with bool
which seems to match your snippet.
这在直觉上似乎是正确的,然而,我认为,即使拥有一个单元化的变量,即使您从未访问过它,也是UB。请参阅文档,特别是bool的示例,它似乎与您的代码片段相匹配。
I feel like you're not correct, but I'm not entirely sure either. These two snippets in the doc seem to contradict: For example, a 1-initialized Vec<T> is considered initialized (under the current implementation; this does not constitute a stable guarantee) because the only requirement the compiler knows about it is that the data pointer must be non-null. Creating such a Vec<T> does not cause immediate undefined behavior, but will cause undefined behavior with most safe operations (including dropping it).
, meaning that initializing the memory to 0xA5 will not cause immediate UB, however
我觉得你说的不对,但我也不能完全肯定。文档中的这两个片段似乎相互矛盾:例如,1初始化的VEC被认为是初始化的(在当前实现下;这不构成稳定的保证),因为编译器知道的唯一要求是数据指针必须为非空。创建这样的VEC不会立即导致未定义的行为,但会导致大多数安全操作(包括丢弃它)的未定义的行为。这意味着将内存初始化为0xA5不会立即导致UB
Mutex
already guarantees exclusive access and allows you to mutate it's interior, the Cell
is unnecessary which is demonstrated by your use of Cell::get_mut(&mut self)
which already takes a mutable reference.
Mutex已经保证了独占访问,并允许您改变它的内部,Cell是不必要的,这从您使用Cell::get_mut(&mut self)得到了证明,它已经接受了一个可变的引用。
Thanks, I found that mut value
assigned by the mutex.lock()
should be sufficient to modify the internal instance. It seems that somebody downvoted my answer just because it wasn't precise enough. :)
谢谢,我发现mutex.lock()分配的mut值应该足以修改内部实例。似乎有人否决了我的回答,只是因为我的回答不够准确。:)
我是一名优秀的程序员,十分优秀!