Why is static mut bad?

The way I see it: so long as you have a single initializer, and so long as it is wrapped in an atomically-backed manner, using pub static mut should be safe. However, things are rarely as they seem. What is going on with static mut that is dangerous even in an atomic context?

1 Like

static mut does not enforce Send or Sync
Basically static mut looks like sugar for

struct StaticMut<T>(pub UnsafeCell<T>);

unsafe impl<T> Send for StaticMut<T> {}
unsafe impl<T> Send for StaticMut<T> {}

static $name: StaticMut<$type> = StaticMut($expr);

Absolutely no guard rails and many ways to fail.
There is nothing extra that you can do with static mut that can't be done with static so it is better to just create a safe wrapper type and stick that in a static

1 Like

It's also worth noting that a wrapper around UnsafeCell is actually safer than static mut too. This is because UnsafeCell's get method only gives you a raw pointer, and leaves it up to you as an API designer to turn that raw pointer into a shared or mutable reference at your discretion. Meanwhile use of a static mut immediately gives you a reference of one kind or the other, which makes it very difficult to ensure that none of those uses overlap in bad ways.

4 Likes

see also https://github.com/rust-lang/rust/issues/53639

1 Like

Even without multithreading, static mut is efficiently a RefCell without ref count. &mut-ing it on two different points in the call stack is insta-UB. This means every non-trivial use case will likely results UB. Are we talking about C?

2 Likes