Repository navigation
Tracking Issue for min_exhaustive_patterns #119612
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; // okIn 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
Activity
Nadrieril commentedon Jan 30, 2024
Hi T-lang! I would like to move forward with this experiment.
Summary of the feature
This feature allows users to omit match arms on empty types when the empty type is matched by-value. This is a guaranteed-sound subset of the
exhaustive_patternsfeature. This allows:If
x: &Result<T, Void>orx: Result<T, &Void>or we're matching behind a pointer/union then this changes nothing (i.e. we require aErr(_)arm like on stable rust). In contrast,exhaustive_patternsallows omitting match arms on empty types in all cases, which can cause UB. For more detail see the OP.Because it's a well-behaved and useful subset, I'd like to move to stabilize it soon. We can think about the rest of
exhaustive_patternslater. Never patterns are a likely part of the final plan, and they need more time (and would benefit from this being stabilized).Proposal
If T-lang approves, I will:
min_exhaustive_patternsinstead ofexhaustive_patternsin rustc, std and tests wherever possible;Nadrieril commentedon Feb 7, 2024
For the triage meeting I prepared step 1 and 2 here: #120742
It appears that the vast majority of in-tree use cases of
exhaustive_patternsare covered bymin_exhaustive_patterns, as hoped.nikomatsakis commentedon Feb 7, 2024
@rfcbot fcp merge
This covers the majority of cases I've encountered myself. I'd be very excited to see this subset move forward. Let's do it!
rfcbot commentedon Feb 7, 2024
Team member @nikomatsakis has proposed to merge this. The next step is review by the rest of the tagged team members:
No concerns currently listed.
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.
scottmcm commentedon Feb 8, 2024
This sounds great. By-value-only covers the two places that I immediately think of for this:
let Ok(x) = foo.try_into();where it's usingError = !, andtry_trait_v2stuff.So I'd be quite happy to start with that and worry about the rest afterwards.
@rfcbot reviewed
34 remaining items
Auto merge of rust-lang#122792 - Nadrieril:stabilize-min-exh-pats2, r…
Nadrieril commentedon Aug 10, 2024
This is now stabilized!
Auto merge of #122792 - Nadrieril:stabilize-min-exh-pats2, r=fee1-dead
Auto merge of #122792 - Nadrieril:stabilize-min-exh-pats2, r=fee1-dead
Rollup merge of rust-lang#139157 - mejrs:never, r=Noratrieb
Unrolled build for rust-lang#139157
Rollup merge of rust-lang#139157 - mejrs:never, r=Noratrieb
Auto merge of #122792 - Nadrieril:stabilize-min-exh-pats2, r=fee1-dead
Auto merge of #122792 - Nadrieril:stabilize-min-exh-pats2, r=fee1-dead