Chapter 19
Patterns and Matching
Patterns are a special syntax in Rust for matching against the structure of types, both complex and simple. Using patterns in conjunction with match expressions and other constructs gives you more control over a program’s control flow.
This chapter is a reference on all things related to patterns: the valid places to use patterns, the difference between refutable and irrefutable patterns, and the different kinds of pattern syntax that you might see. By the end, you’ll know how to use patterns to express many concepts in a clear way.
- 19.1 All the Places Patterns Can Be Used
- 19.2 Refutability: Whether a Pattern Might Fail to Match
- 19.3 Pattern Syntax
The Rust Programming Language · Ch.19 Patterns and Matching
1
Chapter 19 · Introduction
What a pattern is
Definition 19.1 (Pattern). A special syntax in Rust for matching against the structure of types, both complex and simple. In the contexts where patterns are valid, they describe the shape of data.
A pattern consists of some combination of:
- Literals
- Destructured arrays, enums, structs, or tuples
- Variables
- Wildcards
- Placeholders
Some example patterns: x, (a, 3), and Some(Color::Red). Our program matches values against the patterns to determine whether it has the correct shape of data to continue running a particular piece of code.
To use a pattern, we compare it to some value. If the pattern matches the value, we use the value parts in our code. Recall the match expressions in Chapter 6, such as the coin-sorting machine: if the value fits the shape of the pattern, we can use the named pieces; if it doesn’t, the code associated with the pattern won’t run.
The Rust Programming Language · Ch.19 Patterns and Matching
2
Chapter 19 · 19.1 All the Places Patterns Can Be Used
match arms
Patterns pop up in many places; you’ve used them a lot without realizing it. A match is the keyword match, a value to match on, and one or more arms: a pattern plus an expression to run if the value matches it.
Formally:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
From Listing 6.5, matching an Option<i32> in x; the patterns are None and Some(i), left of each arrow:
match x {
None => None,
Some(i) => Some(i + 1),
}
match expressions must be exhaustive: all possibilities for the value must be accounted for. One way is a catch-all pattern in the last arm: a variable name matching any value can never fail and so covers every remaining case.
Definition 19.2 (The _ pattern). A pattern that matches anything but never binds to a variable; often used in the last match arm to ignore any value not specified (more in “Ignoring Values in a Pattern”).
The Rust Programming Language · Ch.19 Patterns and Matching
3
Chapter 19 · 19.1 All the Places Patterns Can Be Used
let statements
Before this chapter we had only explicitly discussed patterns with match and if let, but we’ve used patterns elsewhere too — including in let statements.
A straightforward variable assignment:
let x = 5;
More formally, a let statement looks like this:
let PATTERN = EXPRESSION;
Every time you’ve used a let statement like this, you’ve been using patterns, although you might not have realized it!
- In let x = 5; the variable name in the PATTERN slot is just a particularly simple form of a pattern.
- Rust compares the expression against the pattern and assigns any names it finds.
- x is a pattern that means “bind what matches here to the variable x.” Because the name x is the whole pattern, it effectively means “bind everything to the variable x, whatever the value is.”
The Rust Programming Language · Ch.19 Patterns and Matching
4
Chapter 19 · 19.1 All the Places Patterns Can Be Used
Destructuring a tuple with let
Listing 19.1: Using a pattern to destructure a tuple and create three variables at once
fn main() {
let (x, y, z) = (1, 2, 3);
}
Rust compares the value (1, 2, 3) to the pattern (x, y, z) and sees that the value matches — the number of elements is the same in both — so it binds 1 to x, 2 to y, and 3 to z. Think of this tuple pattern as nesting three individual variable patterns inside it.
If the number of elements in the pattern doesn’t match the number in the tuple, the overall type won’t match and we get a compiler error. Listing 19.2 tries to destructure a three-element tuple into two variables:
Listing 19.2: Incorrectly constructing a pattern whose variables don’t match the number of elements in the tuple
fn main() {
let (x, y) = (1, 2, 3);
}
The Rust Programming Language · Ch.19 Patterns and Matching
5
Chapter 19 · 19.1 All the Places Patterns Can Be Used
The mismatched-tuple type error
Attempting to compile Listing 19.2 results in this type error:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error
The Rust Programming Language · Ch.19 Patterns and Matching
6
Chapter 19 · 19.1 All the Places Patterns Can Be Used
Fixing the mismatch; conditional if let expressions
To fix the error, we could ignore one or more of the tuple’s values using _ or .. (see “Ignoring Values in a Pattern”). If the problem is too many variables in the pattern, make the types match by removing variables so that the number of variables equals the number of elements in the tuple.
Conditional if let expressions
Chapter 6 used if let mainly as a shorter way to write the equivalent of a match that only matches one case. Optionally, if let can have a corresponding else containing code to run if the pattern doesn’t match.
You can also mix and match if let, else if, and else if let expressions. This gives more flexibility than a match, which can express only one value to compare with the patterns. And Rust doesn’t require the conditions in a series of if let, else if, and else if let arms to relate to each other.
Listing 19.3 decides what color to make your background from a series of checks. Its variables have hardcoded values that a real program might receive from user input.
The Rust Programming Language · Ch.19 Patterns and Matching
7
Chapter 19 · 19.1 All the Places Patterns Can Be Used
Mixing if let, else if, and else if let
Listing 19.3: Mixing if let, else if, else if let, and else — src/main.rs
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
The listing continues on the next slide.
The Rust Programming Language · Ch.19 Patterns and Matching
8
Chapter 19 · 19.1 All the Places Patterns Can Be Used
How the background color is chosen
Listing 19.3 (continued) — src/main.rs
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
If the user specifies a favorite color, that color is the background. If no favorite color is specified and today is Tuesday, the background is green. Otherwise, if the user specifies their age as a string and we can parse it as a number successfully, the color is purple or orange depending on the number. If none of these conditions apply, the background is blue.
This conditional structure lets us support complex requirements. With the hardcoded values here, the example prints Using purple as the background color.
The Rust Programming Language · Ch.19 Patterns and Matching
9
Chapter 19 · 19.1 All the Places Patterns Can Be Used
Shadowing and exhaustiveness in if let
if let can shadow variables
if let can introduce new variables that shadow existing ones, in the same way that match arms can. The line if let Ok(age) = age introduces a new age variable containing the value inside the Ok variant, shadowing the existing age.
- So the if age > 30 condition must go within that block.
- We can’t combine the two conditions into if let Ok(age) = age && age > 30: the new age we want to compare to 30 isn’t valid until the new scope starts with the curly bracket.
No exhaustiveness check
The downside of if let expressions is that the compiler doesn’t check for exhaustiveness, whereas with match it does. If we omitted the last else block and so missed handling some cases, the compiler would not alert us to the possible logic bug.
The Rust Programming Language · Ch.19 Patterns and Matching
10
Chapter 19 · 19.1 All the Places Patterns Can Be Used
while let conditional loops
Listing 19.4: Using a while let loop to print values for as long as rx.recv() returns Ok
fn main() {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
for val in [1, 2, 3] {
tx.send(val).unwrap();
}
});
while let Ok(value) = rx.recv() {
println!("{value}");
}
}
Similar in construction to if let, the while let loop lets a while loop run for as long as a pattern continues to match. Listing 19.4 waits on messages sent between threads — checking a Result instead of an Option.
It prints 1, 2, and then 3. recv takes the first message out of the receiver side of the channel and returns Ok(value). In Chapter 16 we unwrapped the error directly, or used the receiver as an iterator in a for loop. We can also use while let: recv returns Ok each time a message arrives, as long as the sender exists, and then Err once the sender side disconnects.
The Rust Programming Language · Ch.19 Patterns and Matching
11
Chapter 19 · 19.1 All the Places Patterns Can Be Used
for loops
In a for loop, the value that directly follows the keyword for is a pattern: in for x in y, the x is the pattern. Listing 19.5 uses a pattern in a for loop to destructure, or break apart, a tuple as part of the loop.
Listing 19.5: Using a pattern in a for loop to destructure a tuple
fn main() {
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
}
We adapt the iterator with the enumerate method so that it produces a value and the index for that value, placed into a tuple. The output follows on the next slide.
The Rust Programming Language · Ch.19 Patterns and Matching
12
Chapter 19 · 19.1 All the Places Patterns Can Be Used
Destructuring in a for loop: the output
The code in Listing 19.5 prints the following:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2
Example 19.3. The first value enumerate produces is the tuple (0, 'a'). When this value is matched to the pattern (index, value), index will be 0 and value will be 'a', printing the first line of the output.
The Rust Programming Language · Ch.19 Patterns and Matching
13
Chapter 19 · 19.1 All the Places Patterns Can Be Used
Function parameters
Function parameters can also be patterns. Listing 19.6 declares a function foo taking one parameter x of type i32 — by now familiar.
Listing 19.6: A function signature using patterns in the parameters
fn foo(x: i32) {
// code goes here
}
fn main() {}
The x part is a pattern! As with let, we could match a tuple in a function’s arguments to the pattern. Listing 19.7 splits the values in a tuple as we pass it to a function.
Listing 19.7: A function with parameters that destructure a tuple — src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
This prints Current location: (3, 5). The values &(3, 5) match the pattern &(x, y), so x is 3 and y is 5.
Remark. Closures are similar to functions (Chapter 13), so closure parameter lists take patterns in the same way.
The Rust Programming Language · Ch.19 Patterns and Matching
14
Chapter 19 · 19.2 Refutability: Whether a Pattern Might Fail to Match
Refutable and irrefutable patterns
You’ve now seen several ways to use patterns, but patterns don’t work the same in every place we can use them. In some places the patterns must be irrefutable; in others they can be refutable. Patterns come in these two forms:
Definition 19.4 (Irrefutable pattern). A pattern that will match for any possible value passed. Example: x in let x = 5; matches anything and therefore cannot fail to match.
Definition 19.5 (Refutable pattern). A pattern that can fail to match for some possible value. Example: Some(x) in if let Some(x) = a_value — if a_value is None rather than Some, the 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.
- if let and while let expressions and the let...else statement accept refutable and irrefutable patterns, but the compiler warns against irrefutable ones: by definition they’re intended to handle possible failure — a conditional’s functionality is its ability to perform differently depending on success or failure.
The Rust Programming Language · Ch.19 Patterns and Matching
15
Chapter 19 · 19.2 Refutability: Whether a Pattern Might Fail to Match
Which constructs accept which patterns
Table 19.1: Pattern refutability required by each construct
| Construct | Accepts |
|---|---|
| Function parameters, let, for | Irrefutable patterns only |
| if let, while let, let...else | Refutable and irrefutable (warning on irrefutable) |
| match arms | Refutable arms; the last arm irrefutable |
In general, you shouldn’t have to worry about the distinction. But you do need to be familiar with refutability so that you can respond when you see it in an error message: then 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.
Next: what happens when we use a refutable pattern where Rust requires an irrefutable one, and vice versa. Listing 19.8 shows a let statement whose pattern is Some(x), a refutable pattern. As you might expect, this code will not compile.
The Rust Programming Language · Ch.19 Patterns and Matching
16
Chapter 19 · 19.2 Refutability: Whether a Pattern Might Fail to Match
A refutable pattern where let needs an irrefutable one
Listing 19.8: Attempting to use a refutable pattern with let
fn main() {
let some_option_value: Option<i32> = None;
let Some(x) = some_option_value;
}
- If some_option_value were 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 complains that we’ve tried to use a refutable pattern where an irrefutable pattern is required (error on the next slide).
The Rust Programming Language · Ch.19 Patterns and Matching
17
Chapter 19 · 19.2 Refutability: Whether a Pattern Might Fail to Match
error[E0005]: refutable pattern in local binding
$ cargo run
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/ch19-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
We didn’t (and couldn’t!) cover every valid value with Some(x), so Rust rightfully produces a compiler error.
The Rust Programming Language · Ch.19 Patterns and Matching
18
Chapter 19 · 19.2 Refutability: Whether a Pattern Might Fail to Match
The fix: let...else
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 let, use let...else. Then, if the pattern doesn’t match, the code in the curly brackets handles the value. Listing 19.9 fixes Listing 19.8.
Listing 19.9: Using let...else and a block with refutable patterns instead of let
fn main() {
let some_option_value: Option<i32> = None;
let Some(x) = some_option_value else {
return;
};
}
Listing 19.10: Attempting to use an irrefutable pattern with let...else
fn main() {
let x = 5 else {
return;
};
}
We’ve given the code an out! Listing 19.9 is perfectly valid, but it means we cannot use an irrefutable pattern without receiving a warning: if we give let...else a pattern that will always match, such as x in Listing 19.10, the compiler warns.
The Rust Programming Language · Ch.19 Patterns and Matching
19
Chapter 19 · 19.2 Refutability: Whether a Pattern Might Fail to Match
warning: unreachable else clause
For Listing 19.10, Rust complains that it doesn’t make sense to use let...else with an irrefutable pattern, because the else will never be reached:
$ cargo run
warning: unreachable `else` clause
--> src/main.rs:2:15
|
2 | let x = 5 else {
| --------- ^^^^
| |
| assigning to binding pattern will always succeed
|
= note: this pattern always matches, so the else clause is unreachable
= 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`
The Rust Programming Language · Ch.19 Patterns and Matching
20
Chapter 19 · 19.2 Refutability: Whether a Pattern Might Fail to Match
Refutability in match arms
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 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.
Example 19.6. Left: the first arm’s literal is refutable; the last arm’s _ is irrefutable and catches every remaining value. Right: a one-arm match with an irrefutable pattern does exactly what the let below it does.
match x {
1 => println!("one"),
_ => println!("anything"),
}
match (1, 2) {
(a, b) => println!("{a} {b}"),
}
let (a, b) = (1, 2);
Now that you know where to use patterns and the difference between refutable and irrefutable patterns, the next section covers all the syntax we can use to create patterns.
The Rust Programming Language · Ch.19 Patterns and Matching
21
Chapter 19 · 19.3 Pattern Syntax
Matching literals
This section gathers all the syntax that is valid in patterns and discusses why and when you might want to use each one.
fn main() {
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
}
As you saw in Chapter 6, you can match patterns against literals directly. The code on the left gives some examples.
It prints one, because the value in x is 1.
This syntax is useful when you want your code to take an action if it gets a particular concrete value.
The Rust Programming Language · Ch.19 Patterns and Matching
22
Chapter 19 · 19.3 Pattern Syntax
Matching named variables
Listing 19.11: A match expression with an arm that introduces a new variable which shadows an existing variable y — src/main.rs
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {y}"),
_ => println!("Default case, x = {x:?}"),
}
println!("at the end: x = {x:?}, y = {y}");
}
Named variables are irrefutable patterns that match any value, and we’ve used them many times. But there is a complication in match, if let, or while let expressions: each starts a new scope, so variables declared as part of a pattern inside them shadow those with the same name outside, as is the case with all variables.
In Listing 19.11, x has the value Some(5) and y the value 10; then we match on x. Look at the patterns in the arms and the println! at the end, and try to figure out what the code will print before reading further.
The Rust Programming Language · Ch.19 Patterns and Matching
23
Chapter 19 · 19.3 Pattern Syntax
Walking through Listing 19.11
- The first arm’s pattern, Some(50), doesn’t match the value of x, so the code continues.
- The second arm’s pattern introduces a new variable y that matches any value inside a Some. We’re in a new scope inside the match, so this is a new y, not the y declared at the beginning with the value 10. It binds to the inner value of the Some in x, which is 5, so that arm prints Matched, y = 5.
- When the match is done, its scope ends, and so does the inner y’s. The last println! produces at the end: x = Some(5), y = 10.
Had x been None, the first two arms wouldn’t match, so the value would match the underscore. That arm’s pattern introduces no x, so the x in its expression is still the outer, unshadowed x, and the match would print Default case, x = None.
Remark. To compare the values of the outer x and y rather than introduce a new variable that shadows y, we need a match guard conditional instead (“Adding Conditionals with Match Guards”, Listing 19.27).
The Rust Programming Language · Ch.19 Patterns and Matching
24
Chapter 19 · 19.3 Pattern Syntax
Multiple patterns with | and ranges with ..=
Matching multiple patterns
In match expressions, | is the pattern or operator. Below, if x matches either value in the first arm, that arm’s code runs:
fn main() {
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
}
This code prints one or two.
Matching ranges of values
The ..= syntax matches an inclusive range of values. When a pattern matches any value within the range, that arm executes:
fn main() {
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
}
The Rust Programming Language · Ch.19 Patterns and Matching
25
Chapter 19 · 19.3 Pattern Syntax
Range patterns: numbers and char values
In the previous example, if x is 1, 2, 3, 4, or 5, the first arm matches. A range is more convenient than | for the same idea, which would need 1 | 2 | 3 | 4 | 5. Specifying a range is much shorter, especially to match, say, any number between 1 and 1,000!
An example using ranges of char values:
fn main() {
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
}
The compiler checks that the range isn’t empty at compile time. The only types for which Rust can tell whether a range is empty are char and numeric values, so ranges are only allowed with numeric or char values.
Rust can tell that 'c' is within the first pattern’s range and prints early ASCII letter.
The Rust Programming Language · Ch.19 Patterns and Matching
26
Chapter 19 · 19.3 Pattern Syntax
Destructuring structs
Listing 19.12: Destructuring a struct’s fields into separate variables — src/main.rs
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
Patterns can also destructure structs, enums, and tuples, so that we can use different parts of these values. Let’s walk through each.
Listing 19.12 shows a Point struct with two fields, x and y, that we break apart using a pattern with a let statement.
The code creates variables a and b that match the values of the x and y fields of p. So the names of the variables in the pattern don’t have to match the struct’s field names.
The Rust Programming Language · Ch.19 Patterns and Matching
27
Chapter 19 · 19.3 Pattern Syntax
Struct field shorthand in patterns
Listing 19.13: Destructuring struct fields using struct field shorthand — src/main.rs
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
It’s common to match the variable names to the field names, to make it easier to remember which variables came from which fields. Because of this, and because let Point { x: x, y: y } = p; contains a lot of duplication, Rust has a shorthand: list only the name of the struct field, and the variables created from the pattern get the same names.
Listing 19.13 behaves like Listing 19.12, but the variables created are x and y instead of a and b: they contain the values from the x and y fields of the p struct.
The Rust Programming Language · Ch.19 Patterns and Matching
28
Chapter 19 · 19.3 Pattern Syntax
Literal values inside struct patterns
We can also destructure with literal values as part of the struct pattern rather than creating variables for all the fields. This lets us test some of the fields for particular values while creating variables to destructure the other fields.
In Listing 19.14, a match separates Point values into three cases: points that lie directly on the x axis (true when y = 0), on the y axis (x = 0), or on neither axis.
Listing 19.14: Destructuring and matching literal values in one pattern — src/main.rs
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
The Rust Programming Language · Ch.19 Patterns and Matching
29
Chapter 19 · 19.3 Pattern Syntax
Points on the x axis, the y axis, or neither
Listing 19.14 (continued) — src/main.rs
match p {
Point { x, y: 0 } => println!("On the x axis at {x}"),
Point { x: 0, y } => println!("On the y axis at {y}"),
Point { x, y } => {
println!("On neither axis: ({x}, {y})");
}
}
}
- The first arm matches any point on the x axis: its y field matches only if its value matches the literal 0. The pattern still creates an x variable that the arm’s code can use.
- The second arm matches any point on the y axis — the x field must be 0 — and creates a variable y for the y field’s value.
- The third arm specifies no literals, so it matches any other Point and creates variables for both the x and y fields.
The Rust Programming Language · Ch.19 Patterns and Matching
30
Chapter 19 · 19.3 Pattern Syntax
First match wins; destructuring enums
In Listing 19.14, the value p matches the second arm by virtue of x containing a 0, so the code prints On the y axis at 7.
Remark. A match expression stops checking arms once it has found the first matching pattern. So even though Point { x: 0, y: 0 } is on the x axis and the y axis, this code would only print On the x axis at 0.
Enums
We’ve destructured enums before (for example, Listing 6.5 in Chapter 6), but haven’t explicitly said that the pattern to destructure an enum corresponds to the way the data stored within the enum is defined.
Listing 19.15 (next slides) uses the Message enum from Listing 6.2 and writes a match with patterns that destructure each inner value.
The Rust Programming Language · Ch.19 Patterns and Matching
31
Chapter 19 · 19.3 Pattern Syntax
Destructuring every Message variant
Each variant holds a different kind of value, and each arm’s pattern mirrors that variant’s definition:
Listing 19.15: Destructuring enum variants that hold different kinds of values — src/main.rs
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.");
}
The Rust Programming Language · Ch.19 Patterns and Matching
32
Chapter 19 · 19.3 Pattern Syntax
Struct-like and tuple-like variant patterns
Listing 19.15 (continued) — src/main.rs
Message::Move { x, y } => {
println!("Move in the x direction {x} and in the y direction {y}");
}
Message::Write(text) => {
println!("Text message: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
}
}
This code prints Change color to red 0, green 160, and blue 255. Try changing the value of msg to see the code from the other arms run.
The Rust Programming Language · Ch.19 Patterns and Matching
33
Chapter 19 · 19.3 Pattern Syntax
How each variant’s pattern is shaped
- No data, like Message::Quit: we can’t destructure the value any further. We can only match on the literal Message::Quit value, and no variables are in that pattern.
- Struct-like, such as Message::Move: a pattern similar to the one for structs. After the variant name, place curly brackets and list the fields with variables, breaking apart the pieces to use in this arm’s code. Here we use the shorthand form, as in Listing 19.13.
- Tuple-like, like Message::Write (a tuple with one element) and Message::ChangeColor (three elements): a pattern similar to the one for tuples. The number of variables in the pattern must match the number of elements in the variant we’re matching.
Nested structs and enums
So far our examples have all matched structs or enums one level deep, but matching works on nested items too! Listing 19.16 refactors Listing 19.15 to support RGB and HSV colors in the ChangeColor message.
The Rust Programming Language · Ch.19 Patterns and Matching
34
Chapter 19 · 19.3 Pattern Syntax
An enum inside an enum
A new Color enum; ChangeColor now holds a Color:
Listing 19.16: Matching on nested enums
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
The Rust Programming Language · Ch.19 Patterns and Matching
35
Chapter 19 · 19.3 Pattern Syntax
Matching two enums in one pattern
Listing 19.16 (continued)
match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("Change color to hue {h}, saturation {s}, value {v}");
}
_ => (),
}
}
The first arm’s pattern matches a Message::ChangeColor variant that contains a Color::Rgb variant, then binds to the three inner i32 values. The second arm also matches Message::ChangeColor, but its inner enum matches Color::Hsv instead. We can specify these complex conditions in one match expression, even though two enums are involved.
The Rust Programming Language · Ch.19 Patterns and Matching
36
Chapter 19 · 19.3 Pattern Syntax
Structs and tuples nested together
We can mix, match, and nest destructuring patterns in even more complex ways. This complicated destructure nests structs and tuples inside a tuple and destructures all the primitive values out:
fn main() {
struct Point {
x: i32,
y: i32,
}
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
}
This code lets us break complex types into their component parts so that we can use the values we’re interested in separately.
Destructuring with patterns is a convenient way to use pieces of values, such as the value from each field in a struct, separately from each other.
The Rust Programming Language · Ch.19 Patterns and Matching
37
Chapter 19 · 19.3 Pattern Syntax
Ignoring values: an entire value with _
It’s sometimes useful to ignore values in a pattern, as in the last arm of a match, to get a catch-all that doesn’t actually do anything but does account for all remaining possible values. There are a few ways to ignore entire values or parts of values: the _ pattern, _ within another pattern, a name that starts with an underscore, or .. to ignore remaining parts of a value.
The underscore is a wildcard pattern that matches any value but doesn’t bind to it. It’s especially useful as the last arm in a match, but we can use it in any pattern, including function parameters:
Listing 19.17: Using _ in a function signature — src/main.rs
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {y}");
}
fn main() {
foo(3, 4);
}
The Rust Programming Language · Ch.19 Patterns and Matching
38
Chapter 19 · 19.3 Pattern Syntax
Why ignore a parameter; parts of a value with a nested _
Listing 19.17 completely ignores the value 3 passed as the first argument and prints This code only uses the y parameter: 4.
In most cases when you no longer need a parameter, you would change the signature so that it doesn’t include it. Ignoring a parameter is especially useful when, for example, you’re implementing a trait that needs a certain type signature but your implementation’s body doesn’t need one of the parameters. You then avoid the compiler warning about unused parameters that you would get if you used a name instead.
Parts of a value with a nested _
We can also use _ inside another pattern to ignore just part of a value — for example, to test only part of a value when the rest is of no use to the code we want to run. Listing 19.18 manages a setting’s value. The business requirements: the user should not be allowed to overwrite an existing customization of a setting, but can unset the setting and give it a value if it is currently unset.
The Rust Programming Language · Ch.19 Patterns and Matching
39
Chapter 19 · 19.3 Pattern Syntax
Don’t overwrite a customized setting
Listing 19.18: Using an underscore within patterns that match Some variants when we don’t need to use the value inside the Some
fn main() {
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {setting_value:?}");
}
The Rust Programming Language · Ch.19 Patterns and Matching
40
Chapter 19 · 19.3 Pattern Syntax
Underscores in one or several places
Listing 19.18 prints Can't overwrite an existing customized value and then setting is Some(5). The first arm doesn’t use the values inside either Some, but tests for both being Some; then it prints why setting_value isn’t changed. In all other cases (either is None), the second arm’s _ lets new_setting_value become setting_value.
Listing 19.19: Ignoring multiple parts of a tuple
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {first}, {third}, {fifth}");
}
}
}
We can also use underscores in multiple places within one pattern to ignore particular values. Listing 19.19 ignores the second and fourth values in a tuple of five items.
It prints Some numbers: 2, 8, 32; the values 4 and 16 are ignored.
The Rust Programming Language · Ch.19 Patterns and Matching
41
Chapter 19 · 19.3 Pattern Syntax
An unused variable starting with _
If you create a variable but don’t use it anywhere, Rust usually issues a warning, because an unused variable could be a bug. But sometimes it’s useful to create a variable you won’t use yet, such as when you’re prototyping or just starting a project. Then you can tell Rust not to warn you by starting the variable’s name with an underscore. Listing 19.20 creates two unused variables, but compiling it gives a warning about only one of them.
Listing 19.20: Starting a variable name with an underscore to avoid getting unused variable warnings — src/main.rs
fn main() {
let _x = 5;
let y = 10;
}
We get a warning about not using the variable y, but no warning about not using _x.
Remark. There is a subtle difference between using only _ and using a name that starts with an underscore. The syntax _x still binds the value to the variable, whereas _ doesn’t bind at all. Listings 19.21 and 19.22 show a case where this distinction matters.
The Rust Programming Language · Ch.19 Patterns and Matching
42
Chapter 19 · 19.3 Pattern Syntax
_s binds and moves; _ never binds
Listing 19.21: An unused variable starting with an underscore still binds the value, which might take ownership of the value.
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{s:?}");
}
We get an error: the s value is still moved into _s, which prevents us from using s again.
Listing 19.22: Using an underscore does not bind the value.
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("found a string");
}
println!("{s:?}");
}
The underscore by itself never binds to the value, so this compiles without errors: we never bind s to anything, so it isn’t moved.
The Rust Programming Language · Ch.19 Patterns and Matching
43
Chapter 19 · 19.3 Pattern Syntax
Remaining parts of a value with ..
Listing 19.23: Ignoring all fields of a Point except for x by using ..
fn main() {
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {x}"),
}
}
With values that have many parts, the .. syntax uses specific parts and ignores the rest, avoiding the need to list underscores for each ignored value. The .. pattern ignores any parts of a value that we haven’t explicitly matched in the rest of the pattern.
In Listing 19.23, Point holds a coordinate in three-dimensional space. The match operates only on the x coordinate and ignores the y and z fields: we list x and then just include ...
That is quicker than listing y: _ and z: _, particularly for structs with lots of fields where only one or two are relevant.
The Rust Programming Language · Ch.19 Patterns and Matching
44
Chapter 19 · 19.3 Pattern Syntax
.. in a tuple: first and last
The syntax .. will expand to as many values as it needs to be. Listing 19.24 shows how to use .. with a tuple.
Listing 19.24: Matching only the first and last values in a tuple and ignoring all other values — src/main.rs
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}
}
The first and last values are matched with first and last. The .. matches and ignores everything in the middle.
The Rust Programming Language · Ch.19 Patterns and Matching
45
Chapter 19 · 19.3 Pattern Syntax
.. must be unambiguous
Using .. must be unambiguous. If it is unclear which values are intended for matching and which should be ignored, Rust gives us an error. Listing 19.25 uses .. ambiguously, so it will not compile.
Listing 19.25: An attempt to use .. in an ambiguous way — src/main.rs
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {second}")
},
}
}
When we compile this example, we get the error shown on the next slide.
The Rust Programming Language · Ch.19 Patterns and Matching
46
Chapter 19 · 19.3 Pattern Syntax
error: .. can only be used once per tuple pattern
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern
--> src/main.rs:5:22
|
5 | (.., second, ..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here
error: could not compile `patterns` (bin "patterns") due to 1 previous error
It’s impossible for Rust to determine how many values in the tuple to ignore before matching a value with second, and how many further values to ignore thereafter. The code could mean: ignore 2, bind second to 4, then ignore 8, 16, and 32; or ignore 2 and 4, bind second to 8, then ignore 16 and 32; and so forth. The name second means nothing special to Rust, so using .. in two places like this is ambiguous — a compiler error.
The Rust Programming Language · Ch.19 Patterns and Matching
47
Chapter 19 · 19.3 Pattern Syntax
Adding conditionals with match guards
Definition 19.7 (Match guard). An additional if condition, specified after the pattern in a match arm, that must also match for that arm to be chosen. Match guards express more complex ideas than a pattern alone allows. They are only available in match expressions, not in if let or while let expressions.
The condition can use variables created in the pattern. In Listing 19.26 the first arm has the pattern Some(x) and the match guard if x % 2 == 0, which is true if the number is even.
Listing 19.26: Adding a match guard to a pattern
fn main() {
let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("The number {x} is even"),
Some(x) => println!("The number {x} is odd"),
None => (),
}
}
The Rust Programming Language · Ch.19 Patterns and Matching
48
Chapter 19 · 19.3 Pattern Syntax
How the match guard decides
- Listing 19.26 prints The number 4 is even. num matches the first arm’s pattern, because Some(4) matches Some(x). Then the match guard checks whether the remainder of dividing x by 2 equals 0; it does, so the first arm is selected.
- Had num been Some(5), the first arm’s guard would be false (5 divided by 2 leaves 1, not 0). Rust would go to the second arm, which matches because it has no match guard and therefore matches any Some variant.
There is no way to express the if x % 2 == 0 condition within a pattern, so the match guard gives us that ability. The downside of this extra expressiveness: the compiler doesn’t try to check for exhaustiveness when match guard expressions are involved.
Solving the shadowing problem
In Listing 19.11, the pattern in the match created a new variable instead of using the variable outside the match, so we couldn’t test against the outer variable’s value. Listing 19.27 uses a match guard to fix this problem.
The Rust Programming Language · Ch.19 Patterns and Matching
49
Chapter 19 · 19.3 Pattern Syntax
Testing against an outer variable
Listing 19.27: Using a match guard to test for equality with an outer variable — src/main.rs
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(n) if n == y => println!("Matched, n = {n}"),
_ => println!("Default case, x = {x:?}"),
}
println!("at the end: x = {x:?}, y = {y}");
}
This code now prints Default case, x = Some(5). The second arm’s pattern doesn’t introduce a new y that would shadow the outer y, so we can use the outer y in the match guard. Instead of Some(y), we specify Some(n): a new variable n that shadows nothing, because there is no n outside the match.
Remark. The match guard if n == y is not a pattern and therefore doesn’t introduce new variables. This y is the outer y rather than a new one shadowing it, so we can look for a value equal to the outer y by comparing n to y.
The Rust Programming Language · Ch.19 Patterns and Matching
50
Chapter 19 · 19.3 Pattern Syntax
Match guards with multiple patterns
You can also use the or operator | in a match guard to specify multiple patterns; the match guard condition applies to all the patterns. Listing 19.28 shows the precedence when combining a pattern that uses | with a match guard. The important part: the if y match guard applies to 4, 5, and 6, even though it might look like if y only applies to 6.
Listing 19.28: Combining multiple patterns with a match guard
fn main() {
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
}
The Rust Programming Language · Ch.19 Patterns and Matching
51
Chapter 19 · 19.3 Pattern Syntax
Precedence of a match guard over |
The arm in Listing 19.28 matches only if x equals 4, 5, or 6 and y is true. When the code runs, the first arm’s pattern matches because x is 4, but the match guard if y is false, so the first arm is not chosen. The code moves on to the second arm, which does match, and the program prints no. The if condition applies to the whole pattern 4 | 5 | 6, not just to the last value 6.
The precedence behaves like this:
(4 | 5 | 6) if y => ...
rather than this:
4 | 5 | (6 if y) => ...
After running the code, the precedence behavior is evident: if the match guard applied only to the final value in the list specified with the | operator, the arm would have matched and the program would have printed yes.
The Rust Programming Language · Ch.19 Patterns and Matching
52
Chapter 19 · 19.3 Pattern Syntax
Using @ bindings
Definition 19.8 (@ binding). The at operator @ creates a variable that holds a value at the same time as we’re testing that value for a pattern match.
In Listing 19.29 we want to test that a Message::Hello id field is within the range 3..=7. We also want to bind the value to the variable id, so that we can use it in the code associated with the arm.
Listing 19.29: Using @ to bind to a value in a pattern while also testing it
fn main() {
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
The Rust Programming Language · Ch.19 Patterns and Matching
53
Chapter 19 · 19.3 Pattern Syntax
Testing a range and capturing the value
Listing 19.29 (continued)
Message::Hello { id: id @ 3..=7 } => {
println!("Found an id in range: {id}")
}
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {id}"),
}
}
This example prints Found an id in range: 5. By specifying id @ before the range 3..=7, we capture whatever value matched the range in a variable named id, while also testing that the value matched the range pattern.
The Rust Programming Language · Ch.19 Patterns and Matching
54
Chapter 19 · 19.3 Pattern Syntax
What each arm of Listing 19.29 can see
- The second arm has only a range in its pattern, so its code has no variable containing the actual value of the id field. The value could have been 10, 11, or 12, but the code that goes with that pattern doesn’t know which: we haven’t saved the id value in a variable.
- The last arm specifies a variable without a range, so the value is available in the arm’s code in a variable named id, thanks to the struct field shorthand syntax. But it applies no test to the value, as the first two arms did: any value would match this pattern.
Table 19.2: Testing versus binding in the three arms of Listing 19.29
| Pattern for the id field | Tests the value? | Value usable in the arm? |
|---|---|---|
| id: id @ 3..=7 | Yes, range 3..=7 | Yes, as id |
| id: 10..=12 | Yes, range 10..=12 | No |
| id (shorthand) | No, any value matches | Yes, as id |
Using @ lets us test a value and save it in a variable within one pattern.
The Rust Programming Language · Ch.19 Patterns and Matching
55
Chapter 19 · Summary
Chapter summary
Rust’s patterns are very useful in distinguishing between different kinds of data. When used in match expressions, Rust ensures that your patterns cover every possible value, or your program won’t compile. Patterns in let statements and function parameters make those constructs more useful, enabling the destructuring of values into smaller parts and assigning those parts to variables. We can create simple or complex patterns to suit our needs.
- Places: match arms, let, if let, while let, for, function and closure parameters.
- Refutability: let, for and parameters need irrefutable patterns; conditionals and let...else take refutable ones.
- Syntax: literals, named variables, |, ..= ranges, destructuring, _, _name, .., match guards, and @ bindings.
Remark. Next, in the penultimate chapter of the book, we look at some advanced aspects of a variety of Rust’s features.
The Rust Programming Language · Ch.19 Patterns and Matching
56