What's the "correct" way of doing static mut in 2024 Rust?

I don't think it should cause directly any UB, but it looks more risky. Besides the requirements that *p is a valid instance of the pointee type, it also introduces an implicit reference &mut *p, which in turn asserts that *p in unaliased during the reference's lifetime. I'm not sure how that would behave in the presence of multiple concurrent pointers, e.g. in a different thread. I think it shouldn't cause any direct issues, since no concurrent writes during the assignment should happen anyway (else we have a data race). But it seems more error-prone, e.g. what if aliasing pointers are used in the right-hand side of the assignment?

I think Tree Borrows is more permissive in that regard: it natively supports two-phased borrows, so creating a temporary reference on assignment shouldn't cause issues.

1 Like

The other difference is that *ptr = val; promises the compiler that there will not be any other references to *ptr in existence across this statement, whereas ptr.write(val) makes no such promise.

This doesn't matter so much in this example, but if instead of val, you have foo(ptr), you're at risk of a surprise, since foo might also dereference ptr, and if it does, you now have two references to *ptr in existence at the same time. And you're most likely to get this wrong with static mut, since I can create a new reference to any visible static mut inside foo().

In contrast, if you do ptr.write(foo(ptr)), and foo(ptr) only ever uses ptr.read and ptr.write, you will never have accidental UB from two references existing at once, because you never have one reference, let alone two.

3 Likes