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 and while 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 variables index and value.
  • let statements can destructure tuples, structs, enums, and bind their parts to variables. For example, let (x, y) = (1, 2); assigns 1 to x and 2 to y.
  • Patterns can also be used in function and closure parameter lists to destructure arguments directly. For example, fn print_coordinates(&(x, y): &(i32, i32)) assigns i32 values to x and y.

Use match to Branch on a Pattern

Rust by example - match

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

Rust by example - 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:

PatternDescription
LiteralsLiterals match exact values like 1, "hello"...
RangesRanges 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 BindingsWhen 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.
DestructuringsDestructuring 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 PatternsThe 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 Bindingsref 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.

References