use core::mem::transmute;
#[derive(Debug)]
#[repr(u8)]
enum MyEnum {
A,
B,
C,
}
const { assert!(size_of::<Option<MyEnum>>() == 1) }
let a = Some(MyEnum::B);
// SAFETY: ???
let t: u8 = unsafe { transmute(a) };
println!("{t}");
// SAFETY: we got `t` from transmuting `Option<MyEnum>`
let b: Option<MyEnum> = unsafe { transmute(t) };
println!("{b:?}");
// SAFETY: we know that `t` corresponds to a `Some` value
let c: MyEnum = unsafe { transmute(t) };
println!("{c:?}");
It passes Miri and I think it should be sound, but it would be nice to have references to back it up. IIUC the niche optimization for Option<MyEnum> is guaranteed, so the const assert should never fail in future compiler releases and enum tags should be the same for Option<MyEnum> and MyEnum.
For context, I want to store Option<MyEnum> in AtomicU8 with efficient codegen for storing and retrieving it.
The niche optimization in this case is not guaranteed. You can read the list of representation guarantees that are provided in std::option - Rust, and Option of a user-defined enum is not one of them. Therefore:
With the size assert!(), then this code is sound, because Option<MyEnum> must contain at least one initialized byte in order to actually store its data, and so if it has only one byte, then the one byte you transmute must be the one that is initialized.
If you do not have the size assert!(), then this code is unsound (but practically, the compiler will catch the mismatch between sizes for you).
Without the size assert!(), it is still sound because transmute() has the same check built in. Though I am not fond of relying on something not compiling in order to implicitly check a soundness condition.
Hm, you are right. It's a bit surprising that #[repr(uN)] enums are not on the list, so I guess the assert could technically break, but it's probably extremely unlikely in practice.
That was only an example. Maybe in practice the compiler is indeed forced to make it one non-padding byte, but this isn't specified so I wouldn't rely on it.
(LLVM has padding bits but Rust does not, although this is not normative yet).
On the tangent of 4 bit ISAs, I got curious and started searching the web. Turns out half a byte is called a nibble, which I think is fantastic. There still seem to exist some microcontrollers with a 4 bit instruction set today. People seemingly also like to build 4 bit CPUs as a hobby. It's a wild world out there.
There are also 16 bit MCUs with dedicated bit access instructions - some just offering single bit access, but others offering "extract" and "deposit" operations (as described in this blog post about bit manipulation on x86-64) which get you into a whole world of weirdness - especially if the MCU's bus protocol lets it tell peripherals which bits it's accessing, not just which bytes.
Haha, I wanted to write that x86 extensions used to be a lot more fun before every new extension turned into basically FMA over increasingly more SIMD lanes. But turns out BMI2 was shipped the same time as AVX2 in 2013. x86 hasn't lost its gusto for extravagant instructions after all I see.