Is &mut *ptr::addr_of_mut!(STATIC_MUT) safer than &mut STATIC_MUT

rust 2024 will make &mut STATIC_mut a hard error.
When I followed the lint , I got

Now that we have ptr::addr_of_mut!, the 2024 edition would be a great time to do @RalfJung's idea from #53639 (comment):

Disallow &MY_STATIC_MUT and &mut MY_STATIC_MUT entirely.

ptr::addr_of_mut!(MY_STATIC_MUT) could even be safe -- unlike taking a reference which is unsafe and often insta-UB -- and helps encourage avoiding races in the accesses, which is more practical than trying to avoid the UB from concurrent existence of references.

(Maybe people will end up just doing &mut *ptr::addr_of_mut!(MY_STATIC_MUT) without thinking through all the safety requirements -- in particular, making sure that the reference stops being used before a second reference is created -- but we can't force them to drink.)

Then following the attached link, I got

You must be able to show that every borrow of the static mut is not reentrant (as opposed to regular interior mutability, which only requires reentrance-freedom when accessing data), which is almost entirely impossible in real-world scenarios.

Seems the dangerous comes from reentrant, for example, in a signal handler.

But why &mut *ptr::addr_of_mut!(STATIC_MUT) could be safer?
Indeed, It seems the post was saying &mut *ptr::addr_of_mut!(STATIC_MUT) is not different with &mut STATIC_MUT:

(Maybe people will end up just doing &mut *ptr::addr_of_mut!(MY_STATIC_MUT) without thinking through all the safety requirements -- in particular, making sure that the reference stops being used before a second reference is created -- but we can't force them to drink.)

and the real requirement is stop using the 1st reference before the 2nd was created.
But If &mut STATIC_MUT is failed to do this, &mut *ptr::addr_of_mut!(MY_STATIC_MUT) is not better either.

And, if a signal handler needing modify some static variable, seems the only sound method is to make sure the variable re-entrance free, which addr_of_mut is helpless for.

And I can't understand

as opposed to regular interior mutability, which only requires reentrance-freedom when accessing data

even with an interior mutability, for code like this:

// assure interior_modify being called in a thread safe context
// so we don't need a mutex here
fn interior_modify(the_ref: &some_wrapper_of_unsafe_cell)
    let mref = unsafe { &mut *the_ref.get() };
    // got signal here
    // and the signal handler call interior_modify too
    mref.modify();
}

is same as &mut STATIC_MUT.

So what is the difference?

1 Like

There is no difference between unsafe { &mut STATIC } and &mut unsafe { *std::ptr::addr_of_mut!(STATIC) }; however, it's less clear how to refactor the former to reduce the amount of unsafe code in your program, whereas the latter cleanly decomposes into let addr = std::ptr::addr_of_mut!(STATIC) and &mut unsafe { *addr }; depending on what you're doing, decomposing into safely taking the address and unsafely turning that address into a reference can help you reason about your code, and/or prove that there are no re-entrant or concurrent accesses to STATIC.

Additionally, because it's now possible to create a pointer without going via a reference, it becomes possible to use ptr::write and ptr::read instead of creating references. These both reduce the risk of you accidentally breaking the rules of Rust, because they ensure that you don't have a reference that exists (which makes it a hazard, even if it's not used) for a longer scope than you expected.

In other words, it's not that unsafe { &mut *std::ptr::addr_of_mut!(STATIC) } is safer than unsafe { &mut STATIC } was; it's that by making you write out the longer form, we reduce the number of places where you need a rule requiring you to use unsafe (since it's the *addr that needs unsafe here), and we make you more likely to notice that there's an alternative way to access your global variable that's safer.

5 Likes

It isn't. It's the same.

You must not be creating references to places within static mut variables, ever. It's impossible to do correctly, per the linked discussions.

The first rule of using static mut is that you shouldn't use it. There are certain niche use cases where you can't do otherwise (or at least alternatives would be just as dangerous), but the vast, vast majority of Rust code doesn't need static mut. Use a static with proper synchronization, or a thread_local.

If you must use static mut, you should exclusively manipulate it through raw pointers. This means using ptr::read and ptr::write functions (or equivalent methods) instead of references and assignments. Yes, this means you generally can't call normal methods on such variables. This is by design. The use of static mut should be reduced to an absolute essential minimum.

Using &mut *static_ptr is almost certainly an error, but at least it's visible in code, and can't happen accidentally, e.g. by calling a method.

5 Likes

Once you've created the &mut, you're out of magical interior mutability land.

Note that only the immutability guarantee for shared references is affected by UnsafeCell. The uniqueness guarantee for mutable references is unaffected. There is no legal way to obtain aliasing &mut, not even with UnsafeCell<T>.

The way that interior mutability is safer is that you can mutate without &mut.

Think atomics, say, as opposed to RefCell.

Quoting this comment:

With unsafe impl Sync [and UnsafeCell], you only have to prove the data accesses correct, but with static mut, the references themselves can conflict.

7 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.