If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.
18.3.1 Matching Literals
Patterns can match literals directly. Take a look at this example:
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
This code prints one because the value in x is 1. This syntax is very useful when you want the code to act on a specific value.
18.3.2 Matching Named Variables
Named variables are irrefutable patterns that can match any value. Take a look:
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}");
The logic in this example is simple; the key point is that there are two y names here. They are unrelated and live in different scopes. The y in let y = 10 is used to store 10, while the y in Some(y) is used to extract the data carried by the Some variant of the Option type.
The execution logic in match is as follows:
The pattern in the first arm does not match the value of
x, so execution continues.The pattern in the second arm introduces a new variable named
y, which matches any value insideSome. Because we are in a new scope inside thematchexpression, this is a newyvariable, not theydeclared at the beginning with value 10. This newybinding matches whatever value is insideSome, and that is what we have inx. Therefore, this newyis bound to the inner value ofSomeinx. That value is5, so the expression in that arm executes and printsMatched, y = 5.If
xwereNoneinstead ofSome(5)—which of course cannot happen in this example—then the patterns in the first two arms would not match, so the value would match_. We do not introduce anxvariable in the wildcard arm, so thexin the expression is still the outerxthat has not been shadowed. In that hypothetical case,matchwould printDefault case, x = None.
Output:
Matched, y = 5
at the end: x = Some(5), y = 10
18.3.3 Multiple Patterns
Inside a match expression, you can use the pipe symbol | syntax, meaning or, to match multiple patterns. Take a look:
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
The first arm in the example matches when x is 1 or 2.
18.3.4 Using ..= to Match a Range of Values
Take a look:
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
The first arm in this example means that when x is any value from 1 to 5 inclusive—namely 1, 2, 3, 4, or 5—it will match.
Because the only types for which Rust can determine whether a range is empty are char and numeric types, ranges are allowed only for numbers or char values. Take a look:
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
The first arm in this example matches characters from a to j, and the second arm matches characters from k to z.
18.3.5 Destructuring to Break Values Apart
We can use patterns to destructure structs, enums, and tuples so that we can refer to different parts of values of those types.
Destructuring structs
Take a look:
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);
}
- The
Pointstruct has two fields,xandy, both of typei32. - There is a
Pointinstance calledp, whosexfield is 0 and whoseyfield is 7. - Then we destructure
pwith a pattern, binding the value ofxtoaand the value ofytob.
This is still a bit verbose. If we change the variable names a to x and b to y, we can shorten it like this:
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);
}
Destructuring can also be used flexibly. Take a look:
fn main() {
let p = Point { x: 0, y: 7 };
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 requires the
xfield to be anything and theyfield to be 0. - The second arm requires the
xfield to be 0 and theyfield to be anything. - The third arm imposes no restrictions on the values of
xandy.
Destructuring enums
Take a look:
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.");
}
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 the color to red {r}, green {g}, and blue {b}")
}
}
}
This code prints Change the color to red 0, green 160, and blue 255.
Destructuring Nested structs and enums
Take a look:
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));
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 data carried by the ChangeColor variant of Message is the Color enum. When using a match expression, just match layer by layer. In the first two arms of the match, the outer layer is the ChangeColor variant, and the inner layer corresponds to the two variants of Color; the values inside can all be extracted through variables.
Destructuring structs and Tuples
Take a look:
struct Point {
x: i32,
y: i32,
}
fn main() {
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
}
The outer layer of the pattern in main is a tuple with two elements:
- The first element is itself a tuple with two elements.
- The second element is a
Pointstruct.
Ignoring Values in Patterns
There are several ways to ignore an entire value or part of a value in a pattern:
-
_: ignore the entire value -
_combined with other patterns: ignore part of a value - names that start with
_ -
..: ignore the remaining part of a value
Using _ to Ignore an Entire Value
Take a look:
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {y}");
}
fn main() {
foo(3, 4);
}
This code completely ignores the value 3 passed as the first parameter and prints This code only uses the y parameter: 4.
Using Nested _ to Ignore Part of a Value
Take a look:
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:?}");
This code prints Can't overwrite an existing customized value and then setting is Some(5). In the first arm, we do not need to match or use the values inside the Some variants, but we do need to determine that setting_value and new_setting_value are Some variants. That is what it means to ignore part of a value.
The second arm means that in all other cases—if setting_value or new_setting_value is None—we assign new_setting_value to setting_value. This is an example of using _ with other patterns to ignore a value.
We can also use underscores in multiple places in one pattern to ignore specific values. Take a look:
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {first}, {third}, {fifth}")
}
}
This ignores the second and fourth elements of the tuple. The code prints Some numbers: 2, 8, 32, and the values 4 and 16 are ignored.
Using Names Starting With _ to Ignore Unused Variables
Take a look:
fn main() {
let _x = 5;
let y = 10;
}
Normally, if you create a variable and do not use it, the Rust compiler warns you. x and y are not used here, but there is a warning for y. That is because _x starts with _, which tells the compiler that this is a temporary variable.
Please note that there is a subtle difference between using only _ and using a name that starts with _. The syntax _x still binds the value to a variable, while _ does not bind anything at all. Take a look:
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{s:?}");
We get an error because the value of s is still moved into _s, which prevents us from printing s.
In this situation, you should use _ to avoid binding the value:
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("found a string");
}
println!("{s:?}");
Using .. to Ignore the Rest of a Value
Take a look:
struct Point {
x: i32,
y: i32,
z: i32,
}
fn main() {
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {x}"),
}
}
When matching with match, we only need the x field, so the pattern writes only x, and the rest is covered by ...
Using .. this way is also fine:
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}
}
This takes only the first and last values and ignores the rest.
This use of .. is not allowed:
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {second}")
},
}
}
Here there is .. in both the front and the back, and we want the middle element. But which element exactly? Written this way, the compiler does not know how many elements .. should skip, so it also does not know which element second refers to.
Output:
$ 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
18.3.6 Using match guards to Add Extra Conditions
match guards are extra if conditions after a match arm pattern. For the arm to match, the condition must also be satisfied. match guards are useful for situations more complex than a plain pattern.
Take a look:
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 => (),
}
}
In the first arm of the match, Some(x) is the pattern, and if x % 2 == 0 is the match guard, which requires the data carried by Some to be divisible by 2.
The condition if x % 2 == 0 cannot be expressed in the pattern itself, so match guards let us express this logic. The downside of this extra expressiveness is that when a match guard is involved, the compiler will not try to check exhaustiveness.
Look at the second example:
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 match guard if n == y is not a pattern, so it does not introduce a new variable. This y is the outer y (with value 10), not a new shadowing y. We can use the comparison to find values n that have the same value as the outer y.
Look at the third example:
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
This example uses match guards together with multiple patterns.
The matching condition says that this arm matches only when x is 4, 5, or 6 and y is true. When this code runs, x is 4, but the match guard y is false, so the first arm does not execute and the second arm prints no.
The important thing to notice here is the precedence of the pattern relative to the match guard. It should be:
(4 | 5 | 6) if y => ...
not:
4 | 5 | (6 if y) => ...
18.3.7 @ Bindings
The @ symbol lets us create a variable that stores a value while we test whether that value matches a pattern.
Take a look:
enum Message {
Hello { id: i32 },
}
fn main() {
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {id_variable}"),
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {id}"),
}
}
In the first arm of this match, the value of the id field is bound to id_variable while also being checked to see whether it falls within the inclusive range from 3 to 7.
Top comments (0)