Description
See https://users.rust-lang.org/t/can-you-break-the-lift/58858 for the original discussion (it actually starts from the 12th post)
The original question takes a &UnsafeCell<T>
and, supposing that nobody is borrowing the inner value, gets a mutable reference (&mut T
) to the inner value, then trasmute it to a &mut UnsafeCell<T>
, creating a &mut UnsafeCell<T>
that alias with another &UnsafeCell<T>
. Is this sound? Does this also mean you can transmute a &UnsafeCell<T>
to a &mut UnsafeCell<T>
(assuming no other reference to the inner value exist)?
Related: jasoncarr0 also showed you can safely do something like this with GhostCell
.
Also related: rust-lang/rust#63787 since that's also caused by a &UnsafeCell<T>
aliasing with a reference to itself, even though in that case it's a reference to the inner value. While that may be solved by storing a raw pointer inside Ref
, it's not an actual possibility here.
I feel like that either this is unsound or the rust aliasing model is fundamentally incompatible with LLVM's noalias.
Activity
phlopsi commentedon May 22, 2021
What you describe is not semantically different from reborrowing, which can be done in purely safe code:
(Playground)
The only difference is, that you have to manage the lifetimes yourself, i.e. Rust won't be complaining, if you do uncomment the last
println!
when usingUnsafeCell
.EDIT:
As for
Assuming
and
have the same semantics, then I'd assume doing
is valid, as well, because
UnsafeCell<T>
is#[repr(transparent)]
. This implies, thatis valid, too.
However, transmuting
&UnsafeCell
to&mut UnsafeCell
is UB according to the Rustonomicon, for the following "reasons":This implies to me, that you must go through
UnsafeCell::<T>::get
, first, before obtaining an exclusive reference to its interior, probably, because the method is what causes the Rust compiler to tag the resulting pointer correctly for LLVM while transmutation does not.matthieu-m commentedon May 22, 2021
It's precisely the lifetime's relationship I find slightly surprising.
Consider this example:
I am surprised to see that
cell.get()
is allowed between the point wherederived
is materialized and the point it's used.A slight version of it is rejected, due to considering that
cell
is still borrowed when callingcell.get_mut()
:RalfJung commentedon May 22, 2021
I think there's a lot of red herrings here. The underlying thing to keep in mind is
Now, as you discovered, sometimes an
&mut T
to the "inside" of an interior mutable data structure can alias an&Foo<T>
to the "outside" of the same data structure. This can happen withGhostCell
, but also withRefCell
. Then once you did that you can turn the inner&mut T
into an&mut UnsafeCell<T>
, but that changes nothing. This is no problem as this can only happen when the "outer" shared reference is not actually usable to access any data.Having aliasing
&UnsafeCell<T>
and&mut UnsafeCell<T>
that are actually both be used to access the underlying data is UB, but that is not what happens here.RalfJung commentedon May 22, 2021
Neither is the case. LLVM
noalias
is about pointer accesses, it says nothing about aliasing of pointers that are not actually used to perform loads or stores.RalfJung commentedon Apr 10, 2022
I'm going to close this --
UnsafeCell
does not do anything on&mut
, but of course it is possible to derive an&mut
from an&UnsafeCell
(likeRefCell::borrow_mut
does, and that's also what happens in the GhostCell example in the OP).