Repository: rust
Issue: Compilation error when matching reference to empty enum
Labels: T-compiler,C-bug,A-patterns,A-exhaustiveness-checking,T-types,F-exhaustive_patterns,T-opsem

> [!NOTE]
> Useful background links which have been shared in comments:
> * The closely related https://github.com/rust-lang/rust/issues/119612#issue-2067102446 was stabilized a couple of months ago.
> * The full https://github.com/rust-lang/rust/issues/51085 feature, [particularly this post by Nadriedel summarising the subtleties](https://github.com/rust-lang/rust/issues/51085#issuecomment-743807861).
> * [Niko’s 2018 blog post](https://smallcultfollowing.com/babysteps/blog/2018/08/13/never-patterns-exhaustive-matching-and-uninhabited-types-oh-my/) on the never pattern for matching uninhabited types, and an "auto-never" desugaring.
> * [Never patterns RFC](https://github.com/rust-lang/rfcs/pull/3719) (in progress as of November 2024)

### Summary
<!--
Thank you for filing a bug report! 🐛 Please provide a short summary of the bug,
along with any information you feel relevant to replicating the bug.
-->

I tried this code:

```rust
enum A {}

fn f(a: &A) -> usize {
    match a {}
}
```

I expected to see this happen: This compiles successfully.

Instead, this happened: (E0004 compiler error, expandable below)
<details><summary>E0004 Compiler Error</summary>
<p>

```text
error[E0004]: non-exhaustive patterns: type `&A` is non-empty
 --> src/lib.rs:4:11
  |
4 |     match a {}
  |           ^
  |
note: `A` defined here
 --> src/lib.rs:1:6
  |
1 | enum A {}
  |      ^
  = note: the matched value is of type `&A`
  = note: references are always considered inhabited
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
  |
4 ~     match a {
5 +         _ => todo!(),
6 +     }
  |

For more information about this error, try `rustc --explain E0004`.
error: could not compile `playground` (lib) due to 1 previous error
```

</p>
</details>

Searching for "Rust E0004" links to docs that don't explain why references to uninhabited types need to be matched: https://doc.rust-lang.org/error_codes/E0004.html - you have to search for "references are always considered inhabited" which takes you to [this issue](https://github.com/rust-lang/rust/issues/78123) from 2020 - more on this history in the Background section below.

### Motivating example

This comes up commonly when creating macros which generate enums, e.g. a very simplified example ([playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=330d4162e876893ae70b6de80fe71983)):

```rust
macro_rules! define_enum {
    (
        $name: ident,
        [
            $(
                $variant: ident => $string_label: expr
            ),* $(,)?
        ]$(,)?
    ) => {
        enum $name {
            $($variant,)*
        }

        impl $name {
            fn label(&self) -> &'static str {
                match self {
                    $(Self::$variant => $string_label,)*
                }
            }
        }
    }
}

// This compiles
define_enum!{
    MyEnum,
    [Foo => "foo", Bar => "bar"],
}

// This fails compilation with E0004
define_enum!{
    MyEmptyEnum,
    [],
}
```

<details><summary>Compiler Error</summary>
<p>

```text
error[E0004]: non-exhaustive patterns: type `&MyEmptyEnum` is non-empty
  --> src/lib.rs:16:23
   |
16 |                   match self {
   |                         ^^^^
...
31 | / define_enum!{
32 | |     MyEmptyEnum,
33 | |     [],
34 | | }
   | |_- in this macro invocation
   |
note: `MyEmptyEnum` defined here
  --> src/lib.rs:32:5
   |
32 |     MyEmptyEnum,
   |     ^^^^^^^^^^^
   = note: the matched value is of type `&MyEmptyEnum`
   = note: references are always considered inhabited
   = note: this error originates in the macro `define_enum` (in Nightly builds, run with -Z macro-backtrace for more info)
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
   |
16 ~                 match self {
17 +                     _ => todo!(),
18 +                 }
   |

For more information about this error, try `rustc --explain E0004`.
error: could not compile `playground` (lib) due to 1 previous error
```

</p>
</details>

The fact that this doesn't work for empty enums is quite a gotcha, and I've seen this issue arise a few times as an edge case. In most cases, it wasn't caught until a few months after, when someone uses the macro to create an enum with no variants.

But why would we ever use such a macro to generate empty enums? Well this can fall out naturally when generating a hierarchy of enums, where some inner enums are empty, e.g.

```rust
define_namespaced_enums! {
    Foo => {
        Fruit: [apple, pear],
        Vegetables: [],
    }
    Bar => {
        Fruit: [banana],
        Vegetables: [carrot],
    }
}
``` 

### Workarounds

Various workarounds include:
* Add a blanket `_ => unreachable!("Workaround for empty enums: references to uninhabited types are considered inhabited at present")`
* In some cases where the enum is copy, use `match *self`
* Use an inner macro which counts the number of variants, and changes the code generated based on whether the enum is empty or not. And e.g. outputs one of:
    * `match *self {}`
    * `match self { ! }` in the body [as suggested by Ralf](https://github.com/rust-lang/unsafe-code-guidelines/issues/413#issuecomment-2401792481) which is non-stable.

### Background

This was previously raised in this issue: https://github.com/rust-lang/rust/issues/78123 but was closed as "expected behaviour" - due to the fact that:
> ... the reason that &Void (where Void is an uninhabited type) is considered inhabited is because unsafe code can construct an &-reference to an uninhabited type (e.g. with ptr::null() or mem::transmute()). I don't know enough to be sure though.

However, when I raised this as a motivating example in https://github.com/rust-lang/unsafe-code-guidelines/issues/413#issuecomment-2399672350, @RalfJung suggested I raise a new rustc issue for this, and that actually the `match` behaviour is independent of the UB semantics decision:

> So please open a new issue (in the rustc repo, not here) about the macro concern. That's a valid point, just off-topic here. Whether we accept that match code is essentially completely independent of what we decide here.

### Meta
<!--
If you're using the stable version of the compiler, you should also check if the
bug also exists in the beta or nightly versions.
-->

`rustc --version --verbose`:
```
rustc 1.81.0 (eeb90cda1 2024-09-04)
binary: rustc
commit-hash: eeb90cda1969383f56a2637cbd3037bdf598841c
commit-date: 2024-09-04
host: aarch64-apple-darwin
release: 1.81.0
LLVM version: 18.1.7
```

Also works on nightly.