So memory itself is untyped, even on the stack. When you write 0x02_u8
to a byte, that byte contains 0x02_u8
, and reading it will produce that value.
Where the potential pitfall is, is when you make a typed copy of that value โ that is, you read the memory as a MaybeUninit<bool>
.
In RalfJ's MiniRust experimental partial formalization, a typed copy consists of essentially decoding the AM-bytes into the abstract value then encoding that abstract value back to AM-bytes at the new location.
So it's a question of defining the decode and encode steps for bool
and for MaybeUninit<bool>
.
bool
is simple enough (minirust/values.md at master ยท RalfJung/minirust ยท GitHub pseudo-Rust):
impl Type {
fn decode(Type::Bool: Self, bytes: List<AbstractByte>) -> Option<Value> {
match *bytes {
[AbstractByte::Init(0, _)] => Value::Bool(false),
[AbstractByte::Init(1, _)] => Value::Bool(true),
_ => throw_ub!(),
}
}
fn encode(Type::Bool: Self, val: Value) -> List<AbstractByte> {
let Value::Bool(b) = val else { unreachable!() };
[AbstractByte::Init(if b { 1 } else { 0 }, None)]
}
}
The question then is how is MaybeUninit
's encode/decode defined, and how does that match with how rustc lowers it to the concrete machine's ABI?
The simple and desirable definition for #[repr(Rust)] union
is that encode/decode just copy the AM-bytes directly.
But unfortunately MaybeUninit<T>
is more complicated, because we want to lower it with the ABI of T
. The simple answer for decode is then to decode { .init: T }
, but if that fails, decode { .uninit: () }
. This would then only preserve through a typed copy AM-bytes which are valid for the wrapped type's decode.
And some amount of this may be necessary โ at a minimum, internal padding can be nonpreserved at the ABI level, so passing MaybeUninit<T>
of such a value cannot preserve the padding bytes if it's passed with the ABI of T
.
The linked discussion is more about #[repr(Rust)] union
but includes some discussion about #[repr(transparent)] union
(but more specifically MaybeUninit
) as well.