Description
The constant evaluation of a move of a union differs from the semantics of non-constant evaluation. The union instance is not regarded as a bag-of-bits as aluded to in the reference and also this unsafe-code-guidelines
issue. This occurs only if the unions has a single scalar scalar field and otherwise ZSTs, which is pretty specific but occurs for MaybeUninit
if the type parameter is a scalar. If the field is of other types such as arrays ([scalar; N]
) or if the other field is not a ZST then the semantics seem fine.
I tried this code:
#![feature(const_if_match)] // Only related to make_1u8_bag, all of them set by Miri
#![feature(const_fn)]
#![feature(const_mut_refs)]
#![feature(const_panic)]
#![feature(const_raw_ptr_deref)]
// Or, equivalently: `MaybeUninit`.
pub union BagOfBits<T: Copy> {
uninit: (),
_storage: T,
}
pub const fn make_1u8_bag<T: Copy>() -> BagOfBits<T> {
assert!(core::mem::size_of::<T>() >= 1);
let mut bag = BagOfBits { uninit: () };
unsafe { (&mut bag as *mut _ as *mut u8).write(1); };
bag
}
pub fn check_bag<T: Copy>(bag: &BagOfBits<T>) {
let val = unsafe { (bag as *const _ as *const u8).read() };
assert_eq!(val, 1);
}
fn main() {
check_bag(&make_1u8_bag::<[usize; 1]>()); // Fine
check_bag(&make_1u8_bag::<usize>()); // Fine
const CONST_ARRAY_BAG: BagOfBits<[usize; 1]> = make_1u8_bag();
check_bag(&CONST_ARRAY_BAG); // Fine.
const CONST_USIZE_BAG: BagOfBits<usize> = make_1u8_bag();
check_bag(&CONST_USIZE_BAG); // Panics
}
I expected to see this happen: All assertions succeed.
Instead, this happened: The check where a union of type BagOfBits<usize>
is moved during constant evaluation, panics. When putting everything through Miri it panics with the message of trying to read an uninitialized byte. The check where the union of type BagOfBits<[usize; 1]>
is used instead runs fine in both cases.
error: Miri evaluation error: attempted to read undefined bytes
--> src/main.rs:21:5
|
21 | assert_eq!(val, 1);
| ^^^^^^^^^^^^^^^^^^^ attempted to read undefined bytes
As far as I am aware, when constant evaluation reads scalars then it propagates uninitialized bytes. If that were to happen then it would explain the observed behaviour but it seems weird why the union copy would use a scalar read.
cc @RalfJung