Closed clarfonthey closed 3 months ago
A type changing transmute for a mutable reference is undefined behaviour in the following cases:
&mut [u8;4]
to &mut u32
would be UB if the reference wasn't 4-aligned).The following is currently an open question whether it would be UB:
It's very much legal to transmute between any kind of reference even if you could "misuse" the reference to write an invalid value. Rust memory isn't typed, and a reference to some memory doesn't know what "type" that memory might be used as until the actual use. In fact, it's not inherently UB to, say, transmute a &mut char
to a &mut u32
and write 0x110000
to it. Exactly when it becomes UB is still up for debate on several issues, but the transmute itself is definitely fine while the former reference is still valid (IE. hasn't been disabled by a foreign read/write).
Hmm, it's interesting that you say that writing 0x11000
isn't necessarily UB in that case; I guess that if the &mut char
actually referred to a value that was originally a u32
, that wouldn't be invalid.
I also when looking more into this noticed that BorrowedBuf
currently relies on this behaviour too, so, that's good to know.
The following is currently an open question whether it would be UB:
- If the referent is an invalid value of the referenced type.
Indeed, that's https://github.com/rust-lang/unsafe-code-guidelines/issues/412.
In fact, it's not inherently UB to, say, transmute a &mut char to a &mut u32 and write 0x110000 to it. Exactly when it becomes UB is still up for debate on several issues
And that's https://github.com/rust-lang/unsafe-code-guidelines/issues/84.
I think those two issues cover all questions you raise? It is definitely allowed to transmute an &mut char
to an &mut u32
and then only ever write valid char
into that; given the Rust's memory is untyped, there's no way we'd ever even notice that something is odd here. (Of course this assumes the aliasing requirements for &mut
are upheld.)
Indeed, I think that my questions are covered by those two; I'll close this. Thank you both!
Essentially, both the reference and MIRI seem to be okay with this, but I don't think this has been explicitly specified, and it probably should be.
Clarifying the question:
x
of arbitrary typeT
U
with the same layout asT
x
is valid for bothT
andU
&mut x
to&mut U
?A few examples for
T
andU
might bechar
andu32
, or more generallyT
andMaybeUninit<T>
, the latter being the case I specifically am interested in.Essentially, we know that in all cases, if you actually assign a value to
x
via&mut U
that isn't valid forT
, that is definitely UB. For example, ifT
ischar
, then it would be invalid to cast the reference of&mut x
to&mut u32
and then assign it the value ofu32::MAX
, for example.For a bit of background: I would like to generalise code such that it can accept either
&mut T
or&mut MaybeUninit<T>
, and ideally, I would be able to cast both of these types to a third type,&mut ForwardInit<T>
, which allows writing values ofT
but does not allow writing values ofMaybeUninit<T>
. This would presumably be sound, since we can't write an uninit value back into&mut T
.However, the issue here is that we would have to define
ForwardInit<T>
like:and, indirectly, we would be casting our
&mut T
to&mut MaybeUninit<T>
. However, we can still technically keep this sound if we ensure that, via our API forForwardInit<T>
, we only allow writing initialized values, and disallow writing uninitialized values.This is kind of similar to
Exclusive<T>
in spirit, since all we're doing is changing the API for the type, without changing anything inside the type. Except in this case, we're actually changing the inside of the type, but making sure that the API on the surface can't actually take advantage of this to do an invalid write. Assuming that it's the write that is UB, and not the reference itself.Here's the code I used to test whether MIRI was okay with this kind of casting:
See the code
```rust #[repr(transparent)] pub struct ForwardInit