RustArena

Match in Rust: pattern matching explained

How Rust's match works: literals, ranges, | alternatives, guards, @ bindings, destructuring tuples, structs and enums, and exhaustiveness.

match compares a value against a list of patterns, top to bottom, and runs the first arm that fits. It's an expression, so it produces a value, and it's exhaustive: every possible value must be covered.

fn describe(code: u16) -> &'static str {
    match code {
        200 => "ok",
        301 | 302 => "redirect",
        400..=499 => "client error",
        500..=599 => "server error",
        _ => "unknown",
    }
}

fn main() {
    for code in [200, 302, 404, 503, 42] {
        println!("{code}: {}", describe(code));
    }
}
200: ok
302: redirect
404: client error
503: server error
42: unknown

Pattern cheat sheet

Pattern Matches
5, 'x', "quit", true that exact literal
1..=9, 'a'..='z' an inclusive range
A | B either pattern
n (a name) anything, and binds it to n
_ anything, binds nothing
(x, 0) a tuple whose second item is 0
Point { x, y: 0 } a struct, binding x, requiring y == 0
Some(v), Err(e) an enum variant, binding its data
[first, .., last] a slice with at least two elements
n @ 1..=9 a range, also binding the value to n
x if x > 100 anything, but only if the guard is true

Destructuring

Patterns take values apart. This works in match, if let, let and function parameters:

match (dx, dy) {
    (0, 0) => "standing still",
    (0, _) => "vertical",
    (_, 0) => "horizontal",
    _ => "diagonal",
}

.. skips the remaining fields of a struct (Player { name, .. }) or items of a tuple or slice.

Exhaustiveness

enum Direction {
    North,
    East,
    South,
    West,
}

fn turn(d: Direction) -> &'static str {
    match d {
        Direction::North => "up",
        Direction::East => "right",
        Direction::South => "down",
    }
}

fn main() {
    println!("{}", turn(Direction::West));
}
error[E0004]: non-exhaustive patterns: `Direction::West` not covered

For integers and strings you can't list every value, so finish with _ => (or a name to use the value). For your own enums, prefer listing every variant so that adding one later points you at every match to update.

match vs if let

When you only care about one pattern, if let is shorter: if let Some(price) = lookup(item) { ... }. Its sibling let ... else handles the "bail out if it doesn't match" case: let Some(user) = find(id) else { return; };. And matches!(value, pattern) gives a bool.

Common mistakes

Example

#[derive(Debug)]
enum Event {
    Click { x: i32, y: i32 },
    Key(char),
    Scroll(i32),
    Resize(u32, u32),
}

fn handle(e: &Event) -> String {
    match e {
        Event::Click { x: 0, y: 0 } => String::from("click at the origin"),
        Event::Click { x, y } if *x < 0 || *y < 0 => format!("click off screen at ({x}, {y})"),
        Event::Click { x, .. } => format!("click in column {x}"),
        Event::Key(c @ ('q' | 'Q')) => format!("quit key {c:?}"),
        Event::Key(c) if c.is_ascii_digit() => format!("digit {c}"),
        Event::Key(c) => format!("key {c}"),
        Event::Scroll(0) => String::from("no scroll"),
        Event::Scroll(n @ 1..) => format!("scroll down {n}"),
        Event::Scroll(n) => format!("scroll up {}", -n),
        Event::Resize(w, h) => format!("resize to {w}x{h}"),
    }
}

fn main() {
    let events = [
        Event::Click { x: 0, y: 0 },
        Event::Click { x: -5, y: 3 },
        Event::Click { x: 40, y: 12 },
        Event::Key('Q'),
        Event::Key('7'),
        Event::Key('a'),
        Event::Scroll(3),
        Event::Scroll(-2),
        Event::Resize(800, 600),
    ];
    for e in &events {
        println!("{:<28} -> {}", format!("{e:?}"), handle(e));
    }

    let scores = [72, 95, 88];
    match scores {
        [first, .., last] => println!("first {first}, last {last}"),
    }
}

Output:

Click { x: 0, y: 0 }         -> click at the origin
Click { x: -5, y: 3 }        -> click off screen at (-5, 3)
Click { x: 40, y: 12 }       -> click in column 40
Key('Q')                     -> quit key 'Q'
Key('7')                     -> digit 7
Key('a')                     -> key a
Scroll(3)                    -> scroll down 3
Scroll(-2)                   -> scroll up 2
Resize(800, 600)             -> resize to 800x600
first 72, last 88

Practice it