| # Pattern and exhaustiveness checking | |
| In Rust, pattern matching and bindings have a few very helpful properties. | |
| The compiler will check that bindings are irrefutable when made and that match arms | |
| are exhaustive. | |
| ## Pattern usefulness | |
| The central question that usefulness checking answers is: | |
| "in this match expression, is that branch redundant?". | |
| More precisely, it boils down to computing whether, | |
| given a list of patterns we have already seen, | |
| a given new pattern might match any new value. | |
| For example, in the following match expression, | |
| we ask in turn whether each pattern might match something | |
| that wasn't matched by the patterns above it. | |
| Here we see the 4th pattern is redundant with the 1st; | |
| that branch will get an "unreachable" warning. | |
| The 3rd pattern may or may not be useful, | |
| depending on whether `Foo` has other variants than `Bar`. | |
| Finally, we can ask whether the whole match is exhaustive | |
| by asking whether the wildcard pattern (`_`) | |
| is useful relative to the list of all the patterns in that match. | |
| Here we can see that `_` is useful (it would catch `(false, None)`); | |
| this expression would therefore get a "non-exhaustive match" error. | |
| ```rust | |
| // x: (bool, Option<Foo>) | |
| match x { | |
| (true, _) => {} // 1 | |
| (false, Some(Foo::Bar)) => {} // 2 | |
| (false, Some(_)) => {} // 3 | |
| (true, None) => {} // 4 | |
| } | |
| ``` | |
| Thus usefulness is used for two purposes: detecting unreachable code (which is useful to the user), | |
| and ensuring that matches are exhaustive (which is important for soundness, | |
| because a match expression can return a value). | |
| ## Where it happens | |
| This check is done anywhere you can write a pattern: `match` expressions, `if let`, `let else`, | |
| plain `let`, and function arguments. | |
| ```rust | |
| // `match` | |
| // Usefulness can detect unreachable branches and forbid non-exhaustive matches. | |
| match foo() { | |
| Ok(x) => x, | |
| Err(_) => panic!(), | |
| } | |
| // `if let` | |
| // Usefulness can detect unreachable branches. | |
| if let Some(x) = foo() { | |
| // ... | |
| } | |
| // `while let` | |
| // Usefulness can detect infinite loops and dead loops. | |
| while let Some(x) = it.next() { | |
| // ... | |
| } | |
| // Destructuring `let` | |
| // Usefulness can forbid non-exhaustive patterns. | |
| let Foo::Bar(x, y) = foo(); | |
| // Destructuring function arguments | |
| // Usefulness can forbid non-exhaustive patterns. | |
| fn foo(Foo { x, y }: Foo) { | |
| // ... | |
| } | |
| ``` | |
| ## The algorithm | |
| Exhaustiveness checking is run before MIR building in [`check_match`]. | |
| It is implemented in the [`rustc_pattern_analysis`] crate, | |
| with the core of the algorithm in the [`usefulness`] module. | |
| That file contains a detailed description of the algorithm. | |
| ## Important concepts | |
| ### Constructors and fields | |
| In the value `Pair(Some(0), true)`, `Pair` is called the constructor of the value, and `Some(0)` and | |
| `true` are its fields. | |
| Every matchable value can be decomposed in this way. | |
| Examples of constructors are: | |
| `Some`, `None`, `(,)` (the 2-tuple constructor), `Foo {..}` (the constructor for a struct `Foo`), | |
| and `2` (the constructor for the number `2`). | |
| Each constructor takes a fixed number of fields; this is called its arity. | |
| `Pair` and `(,)` have arity 2, `Some` has arity 1, `None` and `42` have arity 0. | |
| Each type has a known set of constructors. | |
| Some types have many constructors (like `u64`) or even an infinitely many (like `&str` and `&[T]`). | |
| Patterns are similar: `Pair(Some(_), _)` has constructor `Pair` and two fields. | |
| The difference is that we get some extra pattern-only constructors, namely: | |
| the wildcard `_`, variable bindings, | |
| integer ranges like `0..=10`, and variable-length slices like `[_, .., _]`. | |
| We treat or-patterns separately. | |
| Now to check if a value `v` matches a pattern `p`, we check if `v`'s constructor matches `p`'s | |
| constructor, then recursively compare their fields if necessary. | |
| A few representative examples: | |
| - `matches!(v, _) := true` | |
| - `matches!((v0, v1), (p0, p1)) := matches!(v0, p0) && matches!(v1, p1)` | |
| - `matches!(Foo { a: v0, b: v1 }, Foo { a: p0, b: p1 }) := matches!(v0, p0) && matches!(v1, p1)` | |
| - `matches!(Ok(v0), Ok(p0)) := matches!(v0, p0)` | |
| - `matches!(Ok(v0), Err(p0)) := false` (incompatible variants) | |
| - `matches!(v, 1..=100) := matches!(v, 1) || ... || matches!(v, 100)` | |
| - `matches!([v0], [p0, .., p1]) := false` (incompatible lengths) | |
| - `matches!([v0, v1, v2], [p0, .., p1]) := matches!(v0, p0) && matches!(v2, p1)` | |
| This concept is absolutely central to pattern analysis. | |
| The [`constructor`] module provides functions to extract, list, and manipulate constructors. | |
| This is a useful enough concept that | |
| variations of it can be found in other places of the compiler, like in the MIR-lowering of a match | |
| expression and in some clippy lints. | |
| ### Constructor grouping and splitting | |
| The pattern-only constructors (`_`, ranges and variable-length slices) each stand for a set of | |
| normal constructors, e.g. `_: Option<T>` stands for the set {`None`, `Some`} and `[_, .., _]` stands | |
| for the infinite set {`[,]`, `[,,]`, `[,,,]`, ...} of the slice constructors of arity >= 2. | |
| In order to manage these constructors, we keep them as grouped as possible. | |
| For example: | |
| ```rust | |
| match (0, false) { | |
| (0 ..=100, true) => {} | |
| (50..=150, false) => {} | |
| (0 ..=200, _) => {} | |
| } | |
| ``` | |
| In this example, all of `0`, `1`, .., `49` match the same arms, and thus can be treated as a group. | |
| In fact, in this match, the only ranges we need to consider are: `0..50`, `50..=100`, | |
| `101..=150`,`151..=200` and `201..`. | |
| Similarly: | |
| ```rust | |
| enum Direction { North, South, East, West } | |
| # let wind = (Direction::North, 0u8); | |
| match wind { | |
| (Direction::North, 50..) => {} | |
| (_, _) => {} | |
| } | |
| ``` | |
| Here we can treat all the non-`North` constructors as a group, giving us only two cases to handle: | |
| `North`, and everything else. | |
| This is called "constructor splitting" and is crucial to having exhaustiveness run in reasonable | |
| time. | |
| ### Usefulness vs reachability in the presence of empty types | |
| This is likely the subtlest aspect of exhaustiveness. | |
| To be fully precise, a match doesn't operate on a value; it operates on a place. | |
| In certain unsafe circumstances, it is possible for a place to not contain valid data for its type. | |
| This has subtle consequences for empty types. | |
| Take the following: | |
| ```rust | |
| enum Void {} | |
| let x: u8 = 0; | |
| let ptr: *const Void = &x as *const u8 as *const Void; | |
| unsafe { | |
| match *ptr { | |
| _ => println!("Reachable!"), | |
| } | |
| } | |
| ``` | |
| In this example, `ptr` is a valid pointer pointing to a place with invalid data. | |
| The `_` pattern does not look at the contents of the place `*ptr`, | |
| so this code is ok and the arm is taken. | |
| In other words, despite the place we are inspecting being of type `Void`, there is a reachable arm. | |
| If the arm had a binding however: | |
| ```rust | |
| # #[derive(Copy, Clone)] | |
| # enum Void {} | |
| # let x: u8 = 0; | |
| # let ptr: *const Void = &x as *const u8 as *const Void; | |
| # unsafe { | |
| match *ptr { | |
| _a => println!("Unreachable!"), | |
| } | |
| # } | |
| ``` | |
| Here the binding loads the value of type `Void` from the `*ptr` place. | |
| In this example, this causes UB since the data is not valid. | |
| In the general case, this asserts validity of the data at `*ptr`. | |
| Either way, this arm will never be taken. | |
| Finally, let's consider the empty match `match *ptr {}`. | |
| If we consider this exhaustive, then having invalid data at `*ptr` is invalid. | |
| In other words, the empty match is semantically equivalent to the `_a => ...` match. | |
| In the interest of explicitness, we prefer the case with an | |
| arm, hence we won't tell the user to remove the `_a` arm. | |
| In other words, the `_a` arm is unreachable yet not redundant. | |
| This is why we lint on redundant arms rather than unreachable | |
| arms, despite the fact that the lint says "unreachable". | |
| These considerations only affects certain places, namely those that can contain non-valid data | |
| without UB. | |
| These are: pointer dereferences, reference dereferences, and union field accesses. | |
| We track during exhaustiveness checking whether a given place is known to contain valid data. | |
| Having said all that, the current implementation of exhaustiveness checking does not follow the | |
| above considerations. | |
| On stable, empty types are for the most part treated as non-empty. | |
| The [`exhaustive_patterns`] feature errs on the other end: it allows omitting arms that could be | |
| reachable in unsafe situations. | |
| The [`never_patterns`] experimental feature aims to fix this and | |
| permit the correct behavior of empty types in patterns. | |
| [`check_match`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir_build/thir/pattern/check_match/index.html | |
| [`rustc_pattern_analysis`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_pattern_analysis/index.html | |
| [`usefulness`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_pattern_analysis/usefulness/index.html | |
| [`constructor`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_pattern_analysis/constructor/index.html | |
| [`never_patterns`]: https://github.com/rust-lang/rust/issues/118155 | |
| [`exhaustive_patterns`]: https://github.com/rust-lang/rust/issues/51085 |