18.1.1 What Is a Pattern?
A pattern is a special piece of Rust syntax used to match the structure of complex and simple types.
Using patterns together with match expressions and other constructs gives you better control over program flow.
Patterns are made up of some combination of the following elements:
- literals
- destructured arrays,
enums,structs, and tuples - variables
- wildcards
- placeholders
To use a pattern, you compare it with a value: if the pattern matches, you can use the corresponding part of the value in the code.
18.1.2 match Arms
An arm can use a pattern. Its form is:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
match is required to be exhaustive, meaning you must account for all possible cases.
The _ wildcard is also commonly used in match. It matches anything and does not bind to a variable. It is usually used in the last match arm or when you want to ignore a value.
For a more detailed introduction, see 6.3. The Match Control Flow Operator.
18.1.3 if let Expressions
An if let expression can be thought of as a match expression that matches only one possibility.
It can optionally include:
else ifelseelse if let
The disadvantage of if let compared with match is that it does not check exhaustiveness. If we omit the final else block and therefore miss some cases, the compiler will not warn us about a possible logic error. Take a look:
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");
} 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 used as the background. If no favorite color is specified and today is Tuesday, then the background color is green. Otherwise, if the user specifies their age as a string and we can successfully parse it as a number, then the color is purple or orange depending on the numeric value. If none of those conditions apply, the background color is blue.
This kind of conditional structure lets us support complex requirements. With the hard-coded values here, this example prints Using purple as the background color.
You can see that if let can also introduce shadowing variables in the same way as match: the line if let Ok(age) = age introduces a new shadowing age variable containing the value inside Ok. That means we need to place if age > 30 inside that block: we cannot combine the two conditions into if let Ok(age) = age && age > 30. The shadowing age we want to compare with 30 is only valid inside the new scope that begins with the { braces.
See 6.4. Simple Control Flow - If Let for the rest of the details.
18.1.4 while let Conditional Loop
while let is somewhat similar to if let; as long as the pattern continues to match, it allows the while loop to keep running.
Take a look at this example:
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{top}");
}
This example prints 3, then 2, then 1. The pop method removes the last element from the vector and returns Some(value). If the vector is empty, pop returns None. As long as pop returns Some, the while loop continues running the code in its block. When pop returns None, the loop stops. We can use while let to pop each element from the stack.
18.1.5 for Loops
for loops are the most common loops in Rust. In a for loop, the pattern is the value that comes immediately after the for keyword.
In a for loop, the value that directly follows the keyword for is a pattern. For example, in for x in y, x is the pattern. The following example demonstrates how to use a pattern in a for loop to destructure a tuple as part of the loop:
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
Output:
a is at index 0
b is at index 1
c is at index 2
See 3.6. Control Flow: Loops for the rest of the information.
18.1.6 let Statements
let statements are also patterns, and their syntax is:
let PATTERN = EXPRESSION;
Take a look at this example:
let (x, y, z) = (1, 2, 3);
We match the tuple against the pattern. Rust compares the value (1, 2, 3) to the pattern (x, y, z) and sees that the value matches the pattern, so Rust binds 1 to x, 2 to y, and 3 to z. You can think of this tuple pattern as three separate variable patterns nested inside it.
18.1.7 Function Parameters
Function parameters can also be patterns. Take a look:
fn foo(x: i32) {
// ...
}
The x part is a pattern.
As with let, we can match a tuple in a function parameter against a pattern. For example:
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
Top comments (0)