I know that accessing or writing to static mut
is unsafe because it can lead to data races if multiple threads try to access the data. However, in a strictly single-threaded context, is there any possibility of undefined behavior?
Yes; since you can obtain &'static mut
references from a static mut
variable at any time, you can obtain aliasing mutable references, which results in undefined behavior. For example, this program will print 1
with optimizations enabled, since the compiler assumes that one reference cannot affect the other (Rust Playground):
static mut EXAMPLE: i32 = 0;
fn example(ref1: &mut i32, ref2: &mut i32) -> i32 {
*ref1 = 1;
*ref2 = 2;
*ref1
}
fn main() {
use std::hint::black_box;
let ref1 = black_box(unsafe { &mut EXAMPLE });
let ref2 = black_box(unsafe { &mut EXAMPLE });
println!("{}", example(ref1, ref2));
}
With static mut
variables, you must manually and unsafely uphold the rules for reference lifetimes.
A bit more extended explanation might be found here - The Problem With Single-threaded Shared Mutability - In Pursuit of Laziness.
Another example of UB:
-
Holding onto
&mut
at two levels in the call stack- Run Miri under Tools (top right menu)
- I.e. generally being reentrant
The other examples talk about aliasing being UB, but there's a much simpler example that shows why it is wrong:
static mut VEC: Vec<i32> = Vec::new();
fn main() {
unsafe {
VEC.push(10);
let ten_ref = &VEC[0];
VEC.clear();
println!("{}", *ten_ref);
}
}
Here, you are accessing a value after destroying it with clear
. That's not ok.
The interesting question is that if you didn't need unsafe to access a static mut, would it be safe in a single threaded program, assuming regular safety rules applied.
The answer is that regular rules can't apply! You would have to prove that you don't have illegally overlapping references statically, for example to require that you pass the lack of a lifetime around in calls... which is quite a strange concept.
Much easier to just initialize the value somewhere and pass a reference around, you can use Box::leak if you don't want to worry about lifetime parameters.