Pattern Matching
-
match
Arms#![allow(unused)] fn main() { match VALUE { PATTERN => EXPRESSION, PATTERN => EXPRESSION, PATTERN => EXPRESSION, } }
-
Conditional
if let
statementsfn 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, {}, as the background", color); } 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"); } }
-
while let
loops#![allow(unused)] fn main() { let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("{}", top); } }
-
for
loops#![allow(unused)] fn main() { let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{} is at index {}", value, index); } }
-
let
statements#![allow(unused)] fn main() { let (x, y, z) = (1, 2, 3); }
-
function parameters
fn print_coordinates(&(x, y): &(i32, i32)) { println!("Current location: ({}, {})", x, y); } fn main() { let point = (3, 5); print_coordinates(&point); }
Forms of Pattern
Refutable | Irrefutable |
---|---|
Patterns that can fail to match for some possible value. | Patterns that'll match for any possible value. |
For Example, in if let Some(x) = a_value , the Some(x) can fail to match if a_value is None . | For Example, in let x = 5; , the x matches anything and therefore cannot fail to match. |
The if let and while let expressions accept refutable and irrefutable patterns, but the compiler warns against irrefutable patterns. | Function parameters, let statements, and for loops can only accept irrefutable patterns, because the program cannot do anything meaningful when values don’t match. |
-
Using a refutable pattern where Rust requires an irrefutable pattern:
#![allow(unused)] fn main() { // FAIL: The code doesn't know what to do with a None value // Some(x) is a refutable pattern // let requires an irrefutable pattern let Some(x) = some_option_value; }
-
Using irrefutable pattern where Rust requires a refutable pattern:
#![allow(unused)] fn main() { // WARN: It doesn’t make sense to use if let with an irrefutable pattern // x is an irrefutable pattern // if let is a refutable pattern if let x = 5 { println!("{}", x); }; }
- In
match
statements, all arms use refutable pattern except the last one that uses_
, which uses irrefutable pattern.
Pattern Syntax
Inside the match
expression
-
Each
match
expression creates a new scope, hence varaibles defined in scope will shadow the variables defined outside.#![allow(unused)] fn main() { let x = Some(5); let y = 10; match x { Some(50) => println!("Got 50"), Some(y) => println!("Matched, y = {:?}", y), // This y will shadow the y defined outside of this scope _ => println!("Default case, x = {:?}", x), } println!("at the end: x = {:?}, y = {:?}", x, y); // Output => // Matched, y = 5 // at the end: x = Some(5), y = 10 }
-
It's possible to use the "or" using the
|
syntax#![allow(unused)] fn main() { let x = 1; match x { 1 | 2 => println!("one or two"), 3 => println!("three"), _ => println!("anything"), } // Output => // one or two }
-
You may also specify ranges in the arm, only numbers and
char
values are allowed.#![allow(unused)] fn main() { let x = 5; match x { 1..=5 => println!("one through five"), _ => println!("something else"), } let y = 'c'; match y { 'a'..='j' => println!("early ASCII letter"), 'k'..='z' => println!("late ASCII letter"), _ => println!("something else"), } // Output => // one through five // early ASCII letter }
Destructuring
-
Destructuring structs
struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; // It's possible to destructure a struct into variables let Point { x: a, y: b } = p; assert_eq!(0, a); assert_eq!(7, b); // A shorthand, will directly store respective values in x and y let Point { x, y } = p; assert_eq!(0, x); assert_eq!(7, y); }
-
Destructring as well as matching the structs
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), } }
-
Destructuring 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)); match msg { Message::Quit => { println!("The Quit variant has no data to destructure.") } Message::Move { x, y } => { println!( "Move in the x direction {} and in the y direction {}", x, y ); } Message::Write(text) => println!("Text message: {}", text), Message::ChangeColor(r, g, b) => println!( "Change the color to red {}, green {}, and blue {}", r, g, b ), Message::ChangeColor(Color::Rgb(r, g, b)) => println!( "Change the color to red {}, green {}, and blue {}", r, g, b ), Message::ChangeColor(Color::Hsv(h, s, v)) => println!( "Change the color to hue {}, saturation {}, and value {}", h, s, v ), } }
-
We can mix, match, and nest destructuring patterns in even more complex ways
#![allow(unused)] fn main() { let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 }); }
Ignoring Values
-
You may use
_
or..
to ignore values:_
: It is used when you want to ignore a warning of unused variable, inside match expression for the remaining values or using a name that starts with underscore...
: Ignore remaining parts of the value.
-
You may use
_
for ignoring an unused variablefn main() { let _x = 5; let y = 10; }
-
It's possible to use
_
in functionsfn foo(_: i32, y: i32) { println!("This code only uses the y parameter: {}", y); } fn main() { foo(3, 4); }
-
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.#![allow(unused)] fn main() { // FAIL: s lost it's ownership to _s, but was attempted to use again for printing let s = Some(String::from("Hello!")); // _s binds the value, the value of s is moved if let Some(_s) = s { println!("found a string"); } println!("{:?}", s); }
-
Whereas
_
doesn’t bind at all.#![allow(unused)] fn main() { let s = Some(String::from("Hello!")); // _ never binds the value, hence s stays the owner if let Some(_) = s { println!("found a string"); } println!("{:?}", s); }
-
Note: Ignoring a function parameter can be especially useful in some cases, for example, when implementing a trait when you need a certain type signature but the function body in your implementation doesn’t need one of the parameters. The compiler will then not warn about unused function parameters, as it would if you used a name instead.
-
Ignoring remaining parts of
struct
with..
#![allow(unused)] fn main() { struct Point { x: i32, y: i32, z: i32, } let origin = Point { x: 0, y: 0, z: 0 }; match origin { // It prevents using _ multiple times Point { x, .. } => println!("x is {}", x), } }
-
Skipping middle values using
..
fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (first, .., last) => { println!("Some numbers: {}, {}", first, last); } } }
-
You can only use
..
once per tuplefn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (.., second, ..) => { println!("Some numbers: {}", second) }, } }
Match Guard
-
Match Guard is an additional
if
condition specified after the pattern in a match arm that must also match along with the pattern matching, for that arm to be chosen.#![allow(unused)] fn main() { let num = Some(4); match num { Some(x) if x % 2 == 0 => println!("The number {} is even", x), Some(x) => println!("The number {} is odd", x), None => (), } }
-
The downside of this additional expressiveness is that the compiler doesn't try to check for exhaustiveness when match guard expressions are involved.
-
Using Match Guard with
|
operator#![allow(unused)] fn main() { let x = 4; let y = false; match x { // It works like this => (4 | 5 | 6) if y => ... // And not like this => 4 | 5 | (6 if y) => ... 4 | 5 | 6 if y => println!("yes"), _ => println!("no"), } // Output => // no }
-
@
Bindings#![allow(unused)] fn main() { // The at operator (@) lets us create a variable that holds a value at the same time // we’re testing that value to see whether it matches a pattern. enum Message { Hello { id: i32 }, } let msg = Message::Hello { id: 5 }; match msg { Message::Hello { id: id_variable @ 3..=7, } => println!("Found an id in range: {}", id_variable), // Value of "id" is stored in "id_variable", hence it was knwon here Message::Hello { id: 10..=12 } => { println!("Found an id in another range") // Range was specified, Value of "id" is not stored inside any variable, hence it is unknown here } Message::Hello { id } => println!("Found some other id: {}", id), // Range was not specified, Value of "id" is stored inside "id", hence it was known here } }