Stabilize min_exhaustive_patterns by Nadrieril · Pull Request #122792 · rust-lang/rust (original) (raw)

Stabilisation report

I propose we stabilize the min_exhaustive_patterns language feature.

With this feature, patterns of empty types are considered unreachable when matched by-value. This allows:

enum Void {} fn foo() -> Result<u32, Void>;

fn main() { let Ok(x) = foo(); // also match foo() { Ok(x) => ..., } }

This is a subset of the long-unstable exhaustive_patterns feature. That feature is blocked because omitting empty patterns is tricky when not matched by-value. This PR stabilizes the by-value case, which is not tricky.

The not-by-value cases (behind references, pointers, and unions) stay as they are today, e.g.

enum Void {} fn foo() -> Result<u32, &Void>;

fn main() { let Ok(x) = foo(); // ERROR: missing Err(_) }

The consequence on existing code is some extra "unreachable pattern" warnings. This is fully backwards-compatible.

Comparison with today's rust

This proposal only affects match checking of empty types (i.e. types with no valid values). Non-empty types behave the same with or without this feature. Note that everything below is phrased in terms of match but applies equallly to if let and other pattern-matching expressions.

To be precise, a visibly empty type is:

(An extra change was proposed below: that we ignore #[non_exhaustive] for structs since adding fields cannot turn an empty struct into a non-empty one)

For normal types, exhaustiveness checking requires that we list all variants (or use a wildcard). For empty types it's more subtle: in some cases we require a _ pattern even though there are no valid values that can match it. This is where the difference lies regarding this feature.

Today's rust

Under today's rust, a _ is required for all empty types, except specifically: if the matched expression is of type ! (the never type) or EmptyEnum (where EmptyEnum is an enum with no variants), then the _ is not required.

let foo: Result<u32, !> = ...; match foo { Ok(x) => ..., Err() => ..., // required } let foo: Result<u32, &!> = ...; match foo { Ok(x) => ..., Err() => ..., // required } let foo: &! = ...; match foo { _ => ..., // required } fn blah(foo: (u32, !)) { match foo { _ => ..., // required } } unsafe { let ptr: *const ! = ...; match *ptr {} // allowed let ptr: *const (u32, !) = ...; match *ptr { (x, ) => { ... } // required } let ptr: *const Result<u32, !> = ...; match *ptr { Ok(x) => { ... } Err() => { ... } // required } }

After this PR

After this PR, a pattern of an empty type can be omitted if (and only if):

In all other cases, a _ is required to match on an empty type.

let foo: Result<u32, !> = ...; match foo { Ok(x) => ..., // Err not required } let foo: Result<u32, &!> = ...; match foo { Ok(x) => ..., Err(_) => ..., // required because ! is under a dereference } let foo: &! = ...; match foo { _ => ..., // required because ! is under a dereference } fn blah(foo: (u32, !)) { match foo {} // allowed } unsafe { let ptr: *const ! = ...; match *ptr {} // allowed let ptr: *const (u32, !) = ...; match *ptr { (x, ) => { ... } // required because the matched place is under a (pointer) dereference } let ptr: *const Result<u32, !> = ...; match *ptr { Ok(x) => { ... } Err() => { ... } // required because the matched place is under a (pointer) dereference } }

Documentation

The reference does not say anything specific about exhaustiveness checking, hence there is nothing to update there. The nomicon does, I opened rust-lang/nomicon#445 to reflect the changes.

Tests

The relevant tests are in tests/ui/pattern/usefulness/empty-types.rs.

Unresolved Questions

None that I know of.

try-job: dist-aarch64-apple