You must
- copy the bytes of the discriminant
- copy the non-padding bytes of each field
- zero all bytes that are neither field nor discriminant (or just zero everything first)
One property this technique has is that you can implement it in safe code, which guarantees no UB. (The part that is unsafe is then re-interpreting the produced bytes as a Test
, but writing them is safe.) Here is a demonstration of that:
use core::{ptr, mem};
#[repr(u8)]
#[derive(Debug, PartialEq)]
enum Test {
One(u8) = 1,
Two = 2,
Three(u8, u32) = 3,
}
fn copy_field<T, F, const N: usize>(buf: &mut [u8], whole: &T, field: &F, field_bytes: [u8; N]) {
const {
assert!(size_of::<F>() == N);
}
let offset = ptr::from_ref(field).addr() - ptr::from_ref(whole).addr();
buf[offset..][..N].copy_from_slice(&field_bytes);
}
impl Test {
fn to_bytes_no_uninit(&self) -> [u8; size_of::<Self>()] {
let mut buf = [0; size_of::<Self>()];
let discriminant = match self {
Self::One(x) => {
copy_field(&mut buf, self, x, x.to_ne_bytes());
1
}
Self::Two => 2,
Self::Three(x, y) => {
copy_field(&mut buf, self, x, x.to_ne_bytes());
copy_field(&mut buf, self, y, y.to_ne_bytes());
3
}
};
buf[0] = discriminant;
buf
}
}
fn main() {
let bytes = Test::Three(0xAA, 0xBBCCDDEE).to_bytes_no_uninit();
// Note: This assertion only passes given little-endian and
// align_of::<u32>() == 4, which is typical but not guaranteed.
// Other architectures will have different layouts.
assert_eq!(bytes, [0x03, 0xAA, 0, 0, 0xEE, 0xDD, 0xCC, 0xBB]);
assert_eq!(
unsafe { mem::transmute::<[u8; size_of::<Test>()], Test>(bytes) },
Test::Three(0xAA, 0xBBCCDDEE),
);
}
to_bytes_no_uninit()
will always produce a fully initialized byte array whose contents have the same layout as a Test
value, with all bytes that are padding in the layout initialized to zero instead. It could be made much more elegant with a trait and even a derive macro, but this is the basic principle. It could be made shorter with more use of unsafe
code, but if you choose to do that, you have to be careful that you’re never copying any padding anywhere. This code can be confident it never copies padding because it always uses .to_ne_bytes()
on the primitive field types. If there were nested enums/structs in Test
, it would similarly define functions like Test::to_bytes_no_uninit()
for each enum/struct involved.