Description
This is a tracking issue for the experimental "minimal exhaustive patterns" feature (as per the T-lang experimental feature process).
The feature gate for the issue is #![feature(min_exhaustive_patterns)]
.
Proposal
Pattern-matching on empty types is tricky around unsafe code. For that reason, current stable rust conservatively requires arms for empty types in all but the simplest case. It has long been the intention to allow omitting empty arms when it's safe to do so. The exhaustive_patterns
feature allows the omission of all empty arms, but hasn't been stabilized because that was deemed dangerous around unsafe code.
This feature aims to stabilize an uncontroversial subset of exhaustive_patterns
. Namely: when we're matching on data by-value, then we allow empty arms to be omitted. E.g.:
let x: Result<T, !> = foo();
match x { // ok
Ok(y) => ...,
}
let Ok(y) = x; // ok
In other cases (behind references, pointers or unions) we keep stable behavior i.e. we require arms for the empty cases. This is because these cases may be reachable without UB (either now or in the future, until T-opsem decides otherwise).
unsafe {
let ptr: *const Result<u32, !> = ...;
match *ptr {
Ok(x) => { ... }
Err(_) => { ... } // still required
}
}
let foo: Result<u32, &!> = ...;
match foo {
Ok(x) => { ... }
Err(&_) => { ... } // still required
}
Note: never_patterns
will make these remaining cases more convenient to handle. The two features nicely complement each other.
Comparison with stable 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, an empty type is:
- an enum with no variants;
- the never type
!
; - a struct with a visible field of empty type (and no
#[non_exhaustive]
annotation); - en enum with all variants empty (and no
#[non_exhaustive]
annotation).
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 min_exhaustive_patterns
.
Stable rust
Under stable 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
}
}
min_exhaustive_patterns
With min_exhaustive_patterns
, a pattern of an empty type can be omitted if (and only if):
- the match scrutinee expression has type
!
orEmptyEnum
; - or the empty type is matched by value.
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
}
}
About tracking issues
Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.
Steps
- Implement the feature
- Adjust documentation (see instructions on rustc-dev-guide)
- Stabilization PR (see instructions on rustc-dev-guide)
Unresolved Questions
### Implementation history
- [ ] https://github.com/rust-lang/rust/pull/118803
- [ ] https://github.com/rust-lang/rust/pull/120775
- [ ] https://github.com/rust-lang/rust/pull/120742
- [ ] https://github.com/rust-lang/rust/pull/122255
- [ ] https://github.com/rust-lang/rust/pull/122792