This is wrong. You can't use raw pointers to violate Rust's immutability guarantees, not even via FFI. Doing so is Undefined Behaviour. If a memory region isn't contained in an UnsafeCell
and belongs to an immutable binding, then any way to mutate it is UB.
Your playground example doesn't implement interior mutability, since your binding is mutable. What you are really side-stepping is Rust's aliasing requirements for safe references. It's true that if you are exclusively using raw pointers, you can mostly ignore Rust's aliasing requirements (you still must uphold them at runtime, otherwise it's a data race, but for single-threaded execution it's automatic).
Note that you don't need #![feature(raw_ref_op)]
since creation of raw pointers is already stable: use ptr::addr_of!
and ptr::addr_of_mut!
. Note that the compiler will prevent you from using ptr::addr_of_mut!
on an immutable place. If you try to cheat by using ptr::addr_of!
to create a *const T
and then cast it to *mut T
and write to it, you will get UB (unless you only mutate a part contained in an UnsafeCell
).
&mut T
asserts to the compiler that there are no other live pointers to T
. You can "stack" mutable references by reborrowing the whole or part of it, but you can't use an unrelated or older pointer to access T
. Doing so will invalidate &mut T
. This applies even if you create a &mut T
only to immediately cast it into a raw pointer: the creation step asserts the same invariatns as any other use of a &mut T
.
Thus your first example is UB: the closure needs to use a pointer derived from a unique &mut T
which you create on line 3. But on line 5 you create a new independent &mut x
, invalidating the previous pointers to x
, and thus making it impossible to call the closure.
The second example doesn't cause UB because you don't create &mut T
at any point. UnsafeCell::get
takes a &UnsafeCell<T>
, and &
references don't assert any uniqueness properties (they can even be freely copied). The rest of the operation happen exclusively through raw pointers. Aliasing requirements for raw pointers are much milder, and must be upheld only at runtime (basically, you must not make a data race, anything else is fair game).
Note that you can't do this trick without UnsafeCell
, since normally it is UB to mutate data behind a &
reference. UnsafeCell
is a language-level blessed exception. If you are familiar with C++, it's similar to the mutable
qualifier which allows you to mutate fields and call mutating methods even on an immutable object.