|
| 1 | +# Partial Stabilisation Report |
| 2 | + |
| 3 | +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`](https://github.com/rust-lang/rust/issues/51085) feature allows the omission of all empty arms, but hasn't been stabilized because that was deemed dangerous around unsafe code. |
| 4 | + |
| 5 | +I propose we stabilize an uncontroversial subset of `exhaustive_patterns`. |
| 6 | + |
| 7 | +## Summary |
| 8 | + |
| 9 | +I propose we stabilize the following subset of `exhaustive_patterns`: when the data we're matching on is guaranteed to be valid, then we allow empty arms to be omitted. This covers cases where we match something by value. E.g.: |
| 10 | + |
| 11 | +```rust |
| 12 | +let x: Result<T, !> = foo(); |
| 13 | +match x { // ok |
| 14 | + Ok(y) => ..., |
| 15 | +} |
| 16 | +let Ok(y) = x; // ok |
| 17 | +``` |
| 18 | + |
| 19 | +If the place is not guaranteed to hold valid data (namely ptr dereferences, ref dereferences (conservatively) and union field accesses), then we keep stable behavior i.e. we require arms for the empty cases. |
| 20 | + |
| 21 | +```rust |
| 22 | +unsafe { |
| 23 | + let ptr: *const Result<u32, !> = ...; |
| 24 | + match *ptr { |
| 25 | + Ok(x) => { ... } |
| 26 | + Err(_) => { ... } // still required |
| 27 | + } |
| 28 | +} |
| 29 | +let foo: Result<u32, &!> = ...; |
| 30 | +match foo { |
| 31 | + Ok(x) => { ... } |
| 32 | + Err(&_) => { ... } // still required |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +Note how we conservatively consider that a valid reference can point to invalid data, hence arms of type `&!` and similar cases cannot be omitted. In general we don't allow omitting empty arms when matching behind references. This is a trickier case that I would like to intentionally postpone to later. |
| 37 | + |
| 38 | +Note also that this only allows more code to compile (though it will also raise the "unreachable pattern" lint in more cases). |
| 39 | + |
| 40 | +### Documentation |
| 41 | + |
| 42 | +I have not yet updated relevant documentation, and will do so if the FCP is accepted. |
| 43 | + |
| 44 | +### Tests |
| 45 | + |
| 46 | +The relevant tests are in `tests/ui/pattern/usefulness/empty-types.rs`. |
| 47 | + |
| 48 | +### Unresolved Questions |
| 49 | + |
| 50 | +This intentionally sidesteps the unresolved questions of the feature by stabilizing the uncontroversial subset. |
| 51 | + |
| 52 | + |
| 53 | +## Comparison with stable rust |
| 54 | + |
| 55 | +This proposal only affects match checking of empty types (i.e. types with no valid values, taking visibility into account). The behavior of non-empty types is not changed. Note that everything below is phrased in terms of `match` but applies equallly to `if let` and other pattern-matching expressions. |
| 56 | + |
| 57 | +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. |
| 58 | + |
| 59 | +### Stable rust |
| 60 | + |
| 61 | +Under current 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. |
| 62 | + |
| 63 | +```rust |
| 64 | +let foo: Result<u32, !> = ...; |
| 65 | +match foo { |
| 66 | + Ok(x) => ..., |
| 67 | + Err(_) => ..., // required |
| 68 | +} |
| 69 | +let foo: Result<u32, &!> = ...; |
| 70 | +match foo { |
| 71 | + Ok(x) => ..., |
| 72 | + Err(_) => ..., // required |
| 73 | +} |
| 74 | +let foo: &! = ...; |
| 75 | +match foo { |
| 76 | + _ => ..., // required |
| 77 | +} |
| 78 | +let foo: (u32, !) = ...; // actually impossible but for the sake of example |
| 79 | +match foo { |
| 80 | + _ => ..., // required |
| 81 | +} |
| 82 | +unsafe { |
| 83 | + let ptr: *const ! = ...; |
| 84 | + match *ptr {} // allowed |
| 85 | + let ptr: *const (u32, !) = ...; |
| 86 | + match *ptr { |
| 87 | + (x, _) => { ... } // required |
| 88 | + } |
| 89 | + let ptr: *const Result<u32, !> = ...; |
| 90 | + match *ptr { |
| 91 | + Ok(x) => { ... } |
| 92 | + Err(_) => { ... } // required |
| 93 | + } |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +### After this is stabilized |
| 98 | + |
| 99 | +With this PR, a `_` is required to match on an empty type if (and only if) the empty type is under a dereference or a union field access, *except* if the matched expression is `!` or `EmptyEnum` where we have a backwards-compabitility exception. |
| 100 | + |
| 101 | +In all other cases (a large number), patterns of empty types can be omitted. |
| 102 | + |
| 103 | +```rust |
| 104 | +let foo: Result<u32, !> = ...; |
| 105 | +match foo { |
| 106 | + Ok(x) => ..., // `Err` not required |
| 107 | +} |
| 108 | +let foo: Result<u32, &!> = ...; |
| 109 | +match foo { |
| 110 | + Ok(x) => ..., |
| 111 | + Err(_) => ..., // required because `!` is under a dereference |
| 112 | +} |
| 113 | +let foo: &! = ...; |
| 114 | +match foo { |
| 115 | + _ => ..., // required because `!` is under a dereference |
| 116 | +} |
| 117 | +let foo: (u32, !) = ...; // actually impossible but for the sake of example |
| 118 | +match foo {} // allowed |
| 119 | +unsafe { |
| 120 | + let ptr: *const ! = ...; |
| 121 | + match *ptr {} // allowed for backwards-compatibility |
| 122 | + let ptr: *const (u32, !) = ...; |
| 123 | + match *ptr { |
| 124 | + (x, _) => { ... } // required because the matched place is under a dereference |
| 125 | + } |
| 126 | + let ptr: *const Result<u32, !> = ...; |
| 127 | + match *ptr { |
| 128 | + Ok(x) => { ... } |
| 129 | + Err(_) => { ... } // required because the matched place is under a dereference |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +## Alternatives |
| 135 | + |
| 136 | +I know I can't avoid the `&<empty>` discussion. A possible solution for that case would be: allow omitting empty patterns behind references. The cases most relevant to `unsafe` are unions and pointers anyway, and we won't allow omitting arms in those cases. Moreover having a `&<empty>`, while not technically UB, is more unsafe than having a partially-initialized pointer or union as it's easier to dereference accidentally. |
| 137 | + |
| 138 | +Note that it's not just `&!` (which [could be decided to be uninhabited](https://github.com/rust-lang/unsafe-code-guidelines/issues/413)) but also `&(u32, !)`, which is possibly less likely to be decided to be uninhabited. |
| 139 | + |
| 140 | +## Future possibilities |
| 141 | + |
| 142 | +It is likely that we will never allow omitting empty arms behing pointer dereferences and union field accesses. The alternative for that is [*never patterns*](https://github.com/rust-lang/rust/issues/118155), which are being worked on separately. |
| 143 | + |
| 144 | +The case of references is unsettled, and depends on decisions such as [whether `&!` is uninhabited](https://github.com/rust-lang/unsafe-code-guidelines/issues/413). This is purposefully excluded from the present stabilization. |
0 commit comments