Pattern Matching
Pattern matching is a powerful control flow construct for inspecting data, changing your program's flow based on whether a value conforms to a certain pattern, destructuring the value into its constituent parts, and binding those parts to variables. For example, match
expressions branch code based on different patterns:
// For example, let's match a sample `Result` against two patterns: let res = Ok::<i32, String>(42); match res { // The pattern `Ok(value)` below matches the provided `res`, therefore its expression after `=>` is evaluated: Ok(value) => { println!("Result: {value}"); } // It destructures `Ok` and binds its inner value to the new variable `value`, which is then printed. Err(error) => { eprintln!("Error: {error}"); } // All possible values must be covered. }
You will see complex pattern matching most commonly in match
expressions, but pattern matching can be used in several other places:
if let
andwhile let
expressions are convenient for handling a single pattern or looping as long as a pattern matches, respectively.for
loops can destructure elements from an iterator. For example,for (index, value) in my_vec.iter().enumerate() { ... }
destructures a tuple into two variablesindex
andvalue
.let
statements can destructure tuples, structs, enums, and bind their parts to variables. For example,let (x, y) = (1, 2);
assigns1
tox
and2
toy
.- Patterns can also be used in function and closure parameter lists to destructure arguments directly. For example,
fn print_coordinates(&(x, y): &(i32, i32))
assignsi32
values tox
andy
.
Use match
to Branch on a Pattern
The following code demonstrates pattern matching against an enumeration (enum
):
//! This example defines different types of US coins using an `enum` and then //! uses a `match` expression to determine the value of a given coin in cents, //! demonstrating how `match` can handle different `enum` variants and extract //! data associated with them. /// Define an `enum`. /// Each variant represents a type of US coin. enum Coin { // Unit-like variants (no fields): Penny, Nickel, Dime, /// The `Quarter` is a tuple variant. /// Its field stores the name of a US State. Quarter(String), } /// Returns the value in cents of a given coin. fn value_in_cents(coin: Coin) -> u8 { // The `match` expression takes a value and compares it against a series of // patterns. Each pattern represents a possible structure or value that the // input value might have. It returns the value of the first matching arm. // It's similar to a 'switch' statement in other languages, but it's far // more versatile. match coin { // If the coin is a Penny, return 1 cent. Coin::Penny => 1, // If the coin is a Nickel, return 5 cents. Coin::Nickel => 5, // If the coin is a Dime, return 10 cents. Coin::Dime => 10, // The following is a binding to a variable. // If the coin is a Quarter, which contains a String, // the `state` identifier is assigned the value of the field in the `enum` variant. Coin::Quarter(state) => { println!("State quarter from {state:?}!"); 25 } // Rust's match expressions are exhaustive. This means that you must cover all possible cases. // When needed, you can add a catchall via the `_` pattern. //_ => unreachable!(), } } // `match` is an expression. In this case, it is the last expression in the function's body, thus its value is returned by the function. fn main() { println!("{}", value_in_cents(Coin::Penny)); }
The following example shows pattern matching using a struct
:
// Define a struct named `Point` with three fields: x, y, and z, // all of type `i32`. struct Point { x: i32, y: i32, z: i32, } fn main() { let p = Point { x: 0, y: 0, z: 1 }; // Use `match` to perform pattern matching against a `struct`: match p { // Match against a single `Point` instance: Point { x: 0, y: 0, z: 0 } => println!("p is the origin."), // Match against all Points with x = 0. // The values of `y` and `z` are bound to variables `b` and `c`. Point { x: 0, y: b, z: c } => { println!("x: 0, y: {b}, z: {c}") } // Note the use of a block after `=>`. // The following pattern matches any `Point` struct not caught earlier. // It binds the value of the x field to a variable named `x`. // The `..` syntax means "ignore the other fields". // In this case, `y` and `z` are ignored. Point { x, .. } => println!("x is {x}"), } // Note that the expressions after `=>` return `()`. }
Handle a Single Pattern with if let
if let
is a concise way to match a single pattern:
/// The `if let` syntax allows you to combine `if` and `let` /// to handle values that match a specific pattern, while ignoring others. fn main() { let config_max: Option<u8> = Some(3u8); // If `config_max` is `Some`, bind the inner value to `max` if let Some(max) = config_max { // `max` is available here. println!("The maximum is configured to be {max}."); } // This is equivalent to the following `match` expression: #[allow(clippy::single_match)] match config_max { Some(max) => println!("The maximum is configured to be {max}."), _ => {} // Required because `match` is exhaustive. }; enum Direction { Left, Right, } let left = Direction::Left; // `if let` can match non-parameterized, Unit-like enum variants. // This works even if the enum does not implement `PartialEq`. if let Direction::Left = left { println!("Going left."); } else { // You can also add `else` and `else if` clauses to `if let`. println!("Going right."); } }
Loop as Long as a Pattern Matches with while let
while let
is similar to if let
, but it allows you to loop as long as the pattern continues to match:
fn main() { let mut stack = vec![1, 2, 3]; // `while let` is a control flow construct that allows you to run a loop as // long as a pattern matches. We remove the last element of the vector // at each iteration, until the vector is empty and `pop` returns `None`, // which stops the loop. while let Some(top) = stack.pop() { println!("{top}"); } }
Destructure Elements from an Iterator within a for
Loop
for
loops can destructure elements from an iterator:
fn main() { // Create a `Vec` of tuples. let points = vec![(0, 1), (2, 3), (4, 5)]; // Iterate through the `Vec`. Each element is deconstructed in turn into // variables `x` and `y`. for (x, y) in points { println!("x: {x}, y: {y}"); } }
Destructure during Assignment with let
and let else
let
, beyond simple assignments of a value to a variable, can also destructure - provided that binding to the pattern can't fail:
fn main() { let t: (i32, i32) = (1, 2); // Destructure the tuple into 2 variables: let (x, y) = t; // `x` and `y` are `i32`. println!("x: {x}, y: {y}"); // The destructuring must always succeed (unless there is an `else` clause - // see example below.) // let (3, y) = t; // ERROR: refutable pattern in local binding. // Destructuring with structs: struct Point { x: i32, y: i32, } let origin = Point { x: 0, y: 0 }; let Point { x, y } = origin; // `x` and `y` are `i32`. println!("x: {x}, y: {y}"); // Destructuring with arrays: let arr: [i32; 3] = [1, 2, 3]; let [first, _, third] = arr; // Here, we ignore the second value. // `first` and `third` are `i32`. println!("first: {first}, third: {third}"); // Bind the middle of an array: let arr = [0, 1, 2, 3, 4]; let [start, middle @ .., end] = arr; println!("start: {start}, middle: {middle:?}, end: {end}"); }
With let ... else ...
, a refutable pattern (a pattern that can fail) can match and bind variables in the surrounding scope like a normal let
, or else diverge (e.g. break
, return
, panic!
) when the pattern doesn't match:
fn parse_number(input: &str) -> u32 { // Try to parse the input string. let Ok(num) = input.parse::<u32>() else { // If the binding fails, the `else` block is triggered and the function // panics. panic!("Failed to parse number: {input}"); }; // If it succeeds, `num` is available in the surrounding scope. num } fn main() { let value = parse_number("42"); println!("Parsed value: {value}"); }
Destructure Arguments of a Function or Closure
Destructuring in function or closure parameters lets you unpack complex data types right in their signature, making your code cleaner:
// Destructure a tuple passed as an argument: fn print_coordinates((x, y): (i32, i32)) { println!("x: {x}, y: {y}"); } fn main() { let point = (10, 20); print_coordinates(point); // The same works with closure arguments. // Here we destructure a mutable reference into `x`, a `i32` variable. // Note the second `mut` to make `x` mutable within the closure! let add_one = |&mut mut x| -> i32 { x += 1; x }; let a: &mut i32 = &mut 3; let b = add_one(a); println!("a: {a}, b: {b}"); }
Pattern Syntax
The following table summarizes possible patterns, which, of course, can be combined with one another:
Pattern | Description |
---|---|
Literals | Literals match exact values like 1 , "hello"... |
Ranges | Ranges work for char and integer types. They match a value within an interval: 1..6 (1 to 5, 6 is excluded) or 1..=5 (1 to 5 inclusive) or 'a'..='z' . |
Variable Bindings | When a pattern matches, it can bind (parts of) the value to new variables. For example, Some(data) binds an Option 's inner value to the data variable. |
Destructurings | Destructuring break down structs, enums, and tuples into their components. For example, for a Point struct , use Point { x: a, y: b } to destructure its fields into the a and b variables. For enums, use e.g. MyEnum::Variant(a, b) or MyEnum::Variant2 { x: a, y: b } ; for tuples, use e.g. (first, second,) to retrieve the first and second components of a 2-tuple. |
Ignoring Values | _ ignores a single value without binding. For example, (first, _) binds the first component of a tuple only. _name , a variable name starting with an underscore, binds a value but the compiler won't warn if it's unused, signaling an intentional non-use. .. ignores multiple remaining items in a tuple, struct, or slice. For example, Point3D { x: 0, .. } matches any Point3D with x = 0, regardless of the values of y and z . |
Or Patterns | The character \| between subpatterns match if any one of the subpatterns matches. 1 \| 2 \| 3 matches 1, 2, or 3. All patterns in an "or" must bind variables of the same name and type, if they do. |
Match Guards (if conditions) | Match guards add an additional conditional check to a pattern arm. The pattern must match, and the guard's if condition must be true for the arm to be chosen. |
@ Bindings to Subpatterns | @ allow you to bind a variable name to a value while also testing that value against a further pattern. The syntax is variable_name @ pattern , e.g., id_val @ 3..=7 . |
ref and ref mut Bindings | ref or ref mut create references (&T or &mut T ) to (parts of) the matched value, rather than moving or copying them. This is useful when you want to borrow parts of a value. Note that Rust's "match ergonomics" often make explicit ref and ref mut unnecessary when matching on references. |
Match against Literals
You can match against literal values like 1
, "Rust", true
, (0, 0)
, [1, 2]
, or Some(42)
directly:
fn main() { let x = 1; match x { 1 => println!("one"), 2 => println!("two"), _ => println!("something else"), } }
Match against a Range
Ranges match a value between two numbers either inclusively (start..=end
) or exclusively at the upper bound (start_included..end_not_included
). Ranges work for both char
and integer types:
fn main() { let x = 5; match x { 1..=5 => println!("one through five"), 6..10 => println!("six through nine"), _ => println!("something else"), } }
Bind to Variables
Patterns can bind parts of a matched value to new variables:
fn main() { let x: Option<i32> = Some(5); if let Some(val) = x { // Declares a `val` variable and bind it to the inner vaue of `x`. println!("Got a value: {val}"); } // `val` falls out of scope at the end of the block. // println!("{val}"); // ERROR: cannot find value `val` in this scope. }
Destructure
Patterns can destructure structs, enums, tuples, and references.
Destructure Structs
The pattern to destructure a struct is similar to the struct
assignment syntax:
#[derive(Clone, Copy)] struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; // Destructure `p` into variables `a` and `b` in a `let` statement: let Point { x: a, y: b } = p; println!("a: {a}, b: {b}"); // Destructure `p` into `x` and `y` variables (shortcut): let Point { x, y } = p; assert_eq!(x, 0); assert_eq!(y, 7); // Destructure `p` in a `match` arm: 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})"), } }
Destructure Enums
You can destructure enums variant by variant and bind their fields, if any, to variables:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), } fn main() { let msg = Message::Write(String::from("hello")); match msg { Message::Write(text) => println!("Text message: {text}"), Message::Move { x, y } => println!("Move to x: {x}, y: {y}"), Message::Quit => println!("Quit"), // Must be exhaustive. } }
Destructure Tuples
Tuples can be destructured into their components:
fn main() { let pair = (0, -5); match pair { (0, y) => println!("First element is 0, y = {y}."), (x, 0) => println!("Second element is 0, x = {x}."), _ => println!("It doesn't matter what they are."), } }
Destructure References
References can be destructured as well:
fn main() { // Given a reference... let reference: &i32 = &42; // ...we can destructure it into its pointed-to type. // Note that `&` is on the left side. let &value = reference; // `value` is `i32`: println!("Type of `value`: {}", std::any::type_name_of_val(&value)); assert_eq!(value, 42); // This is equivalent to dereferencing (following the pointer): let _value: i32 = *reference; // Note that it won't work if the underlying type is not `Copy`: struct NotCopyStruct; let _reference = &NotCopyStruct; // let &value = _reference; // Error[E0507]: cannot move out of `*reference` // which is behind a shared reference. }
Ignore Values
_
ignores a single value.- A variable starting with
_
e.g.,_name
, binds the value but silences unused variable warnings. ..
ignores remaining parts of a struct, tuple, or slice.
fn main() { // `_unused` is bound to the value `42`, but the compiler does not complain // if it is not used later. let _unused = 42; let numbers = (2, 4, 8, 16, 32); // Use `..` to ignore e.g. the middle elements: let (first, .., last) = numbers; println!("Some numbers: first = {first}, last = {last}"); // Use `_` to ignore a value completely: fn foo(_: i32, y: i32) { // The first parameter is ignored. println!("y is: {y}"); } foo(42, 43); }
Use an Or (|
) to Match Multiple Patterns
A sequence of patterns separated by |
match if any of the sub-patterns match. All sub-patterns must bind variables of the same name and type:
fn main() { let x = 3; match x { 1 | 2 => println!("one or two"), 3 | 4 => println!("three or four"), _ => println!("something else"), } }
Bind a Variable with @
Bindings (Bind to Subpatterns)
variable @ pattern
binds a variable name to a value while also testing that value against a further pattern:
enum Message { Hello { id: i32 }, } fn main() { let msg = Message::Hello { id: 5 }; match msg { // The `id_val` variable is bound to `msg.id` if it's in 3..=7. Message::Hello { id: id_val @ 3..=7 } => { println!("Found an id in range: {id_val}."); } Message::Hello { id: 10..=12 } => { println!("Found an id in another range (no binding here)."); } Message::Hello { id } => { println!("Found some other id: {id}."); } } }
Match Guards (if condition
)
Match guards add an additional conditional check to a pattern:
fn main() { let num = Some(4); match num { Some(x) if x < 0 => println!("Negative: {x}"), Some(x) if x % 2 == 0 => println!("Even: {x}"), Some(x) => println!("Odd positive: {x}"), None => (), } }
Create References While Binding with ref
and ref mut
Variable binding moves or copies a matched value into the new variable. ref
(or ref mut
) create (mutable) references instead:
//! Variable binding moves or copies the bound value by default. //! Use `ref` or `ref mut` to get a reference instead. //! //! `ref` and `ref mut` are most often used in `match`, `if let`, and `while //! let` expressions. fn main() { // A sample tuple struct with an inner field that is not `Copy`: #[derive(Debug)] struct My(String); let my = My(String::from("Hello")); match my { My(string) => { // `string` is moved here. println!( "Type of `string`: {}", std::any::type_name_of_val(&string) ); } // `string` is dropped. } // Hence `my` can't be used anymore. // println!("{my:?}"); // error[E0382]: borrow of partially moved value: `my`. // To obtain a reference from a value instead of moving it, // you can use the `ref` keyword, which modifies the binding, // so that a reference is created for the value, then the reference is // assigned to the new variable. let mut robot_name = Some(String::from("Bender")); #[allow(clippy::single_match)] match robot_name { Some(ref name) => println!("Name (immutable ref): {name}"), /* `name` is `&String` */ None => (), } // Use `ref mut` to obtain a mutable reference: if let Some(ref mut name_mut) = robot_name { name_mut.push_str(" Rodriguez"); /* `name_mut` is `&mut String` */ } // `robot_name` remains available, since we did not move its contents. println!("Full name: {robot_name:?}"); // Some("Bender Rodriguez") // Note that `ref` works in `let` bindings as well, but its use is // discouraged: let value = 80; // UGLY: let ref port = value; // BETTER: let _port: &i32 = &value; }
Note that Rust's "match ergonomics" often make explicit ref
and ref mut
unnecessary when matching on references.
See Also
- Control Flow.
- Error Handling.
- Option.
- Result.
- Rust Patterns.