Refutability: Whether a Pattern Might Fail to Match
Patterns come in two forms: refutable and irrefutable. Patterns that will match
for any possible value passed are irrefutable. An example would be x
in the
statement let x = 5;
because x
matches anything and therefore cannot fail
to match. Patterns that can fail to match for some possible value are
refutable. An example would be Some(x)
in the expression if let Some(x) = a_value
because if the value in the a_value
variable is None
rather than
Some
, the Some(x)
pattern will not match.
Function parameters, let
statements, and for
loops can only accept
irrefutable patterns, because the program cannot do anything meaningful when
values don’t match. The if let
and while let
expressions and the
let
-else
statement accept refutable and irrefutable patterns, but the
compiler warns against irrefutable patterns because by definition they’re
intended to handle possible failure: the functionality of a conditional is in
its ability to perform differently depending on success or failure.
In general, you shouldn’t have to worry about the distinction between refutable and irrefutable patterns; however, you do need to be familiar with the concept of refutability so you can respond when you see it in an error message. In those cases, you’ll need to change either the pattern or the construct you’re using the pattern with, depending on the intended behavior of the code.
Let’s look at an example of what happens when we try to use a refutable pattern
where Rust requires an irrefutable pattern and vice versa. Listing 19-8 shows a
let
statement, but for the pattern we’ve specified Some(x)
, a refutable
pattern. As you might expect, this code will not compile.
If some_option_value
was a None
value, it would fail to match the pattern
Some(x)
, meaning the pattern is refutable. However, the let
statement can
only accept an irrefutable pattern because there is nothing valid the code can
do with a None
value. At compile time, Rust will complain that we’ve tried to
use a refutable pattern where an irrefutable pattern is required:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0005]: refutable pattern in local binding
--> src/main.rs:3:9
|
3 | let Some(x) = some_option_value;
| ^^^^^^^ pattern `None` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
= note: the matched value is of type `Option<i32>`
help: you might want to use `let else` to handle the variant that isn't matched
|
3 | let Some(x) = some_option_value else { todo!() };
| ++++++++++++++++
For more information about this error, try `rustc --explain E0005`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error
Because we didn’t cover (and couldn’t cover!) every valid value with the
pattern Some(x)
, Rust rightfully produces a compiler error.
If we have a refutable pattern where an irrefutable pattern is needed, we can
fix it by changing the code that uses the pattern: instead of using let
, we
can use if let
. Then if the pattern doesn’t match, the code will just skip
the code in the curly brackets, giving it a way to continue validly. Listing
19-9 shows how to fix the code in Listing 19-8.
We’ve given the code an out! This code is perfectly valid now. However,
if we give if let
an irrefutable pattern (a pattern that will always
match), such as x
, as shown in Listing 19-10, the compiler will give a
warning.
Rust complains that it doesn’t make sense to use if let
with an irrefutable
pattern:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
warning: irrefutable `if let` pattern
--> src/main.rs:2:8
|
2 | if let x = 5 {
| ^^^^^^^^^
|
= note: this pattern will always match, so the `if let` is useless
= help: consider replacing the `if let` with a `let`
= note: `#[warn(irrefutable_let_patterns)]` on by default
warning: `patterns` (bin "patterns") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/patterns`
5
For this reason, match arms must use refutable patterns, except for the last
arm, which should match any remaining values with an irrefutable pattern. Rust
allows us to use an irrefutable pattern in a match
with only one arm, but
this syntax isn’t particularly useful and could be replaced with a simpler
let
statement.
Now that you know where to use patterns and the difference between refutable and irrefutable patterns, let’s cover all the syntax we can use to create patterns.