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.