Zero Variant Enums Break #[derive(Debug)]


#1

So I was playing around with zero-sized types and zero-variant enums and I initially tried this:

enum Foo {}

let zero_sized: Foo = unsafe { std::mem::transmute(()) };
match zero_sized {
    
}

and this breaks on runtime as we are using unsafe, (But this isn’t because we are using unsafe, it doesn’t break at the unsafe, if we change the code to do nothing with the Foo we get no UB)

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.44s
     Running `target/debug/playground`
timeout: the monitored command dumped core
/root/entrypoint.sh: line 8:     7 Illegal instruction     timeout --signal=KILL ${timeout} "$@"

But then I remembered that there were zero-variant enums in the wild (If I recall, this is how libc::c_void works) and I wondered what would happen if I should make Foo #[derive(Debug)]:

#[derive(Debug)]
enum Foo{}

let zero_sized: Foo = unsafe { std::mem::transmute(()) };
println!("{:?}", zero_sized);

and similarly this doesn’t work either:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.46s
     Running `target/debug/playground`
timeout: the monitored command dumped core
/root/entrypoint.sh: line 8:     7 Illegal instruction     timeout --signal=KILL ${timeout} "$@"

Which, I presume is because the Debug meta proc-macro generates this code:

impl std::fmt::Debug for Foo {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            
        }
    }
}

and as we saw previously this breaks, but then I tried the following code:

enum Foo {}

let sized_foo: Foo = unsafe { std::mem::transmute(()) };
match sized_foo {
   _ => println!("Works!"),
}

and we get Works! printed to the console, so I am wondering why the derive(Debug) proc-macro doesn’t generate this code in case of it working on a zero-variant enum:

impl std::fmt::Debug for Foo {
    fn fmt(&self, f: &mut std::fmt::Formatter -> std::fmt::Result {
        match self {
            _ => write!(f, "Foo"),
        }
    }
}

Please note as well, that I have actually not seen the proc-macro code for Debug's derive so it might be a limitation, and if so, please let me know.


#2

Constructing enum value which doesn’t match with any of its inhabitants is UB. In this case Foo doesn’t have any inhabitants so it should never exist at runtime.

For people who’s not familiar with the term “UB”, it means your code has behavior not defined in the spec, which means the compiler is explicitly allowed to launch a missile to your head.


#3

Ah, that explains why it works with a zero-size struct


#4

Nope.