Rust Match

The match expression is Rust's pattern-matching control flow operator, and unlike a switch statement in other languages, the compiler forces you to account for every possible value up front.

Why match Instead of if/else

match compares a value against a series of patterns, top to bottom, and runs the first arm whose pattern fits. It supports exact values, ranges, multiple alternatives with |, and — unlike if/else chains — the compiler checks that every possible input value is handled.

Basic match on an integer

fn main() {
    let dice_roll = 4;

    match dice_roll {
        1 => println!("Snake eyes... well, one die anyway."),
        2 | 3 => println!("Low roll."),
        4..=5 => println!("Decent roll."),
        6 => println!("Maximum roll!"),
        _ => println!("Not a valid six-sided die value."),
    }
}
Note: match requires every possible value of the matched type to be covered. Omitting a case — and omitting a _ catch-all — is a compile-time error, not a runtime surprise. This is one of Rust's strongest guarantees against forgotten edge cases.

Matching on Enums and Binding Data

match really shines with enums: it can destructure each variant and bind its inner data to local variables in the same pattern, so you never need a separate step to "unwrap" the value first.

Destructuring enum variants

enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
    Triangle { base: f64, height: f64 },
}

fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
        Shape::Rectangle(width, height) => width * height,
        Shape::Triangle { base, height } => 0.5 * base * height,
    }
}

fn main() {
    let shapes = vec![
        Shape::Circle(2.0),
        Shape::Rectangle(3.0, 4.0),
        Shape::Triangle { base: 6.0, height: 2.0 },
    ];

    for shape in &shapes {
        println!("Area: {:.2}", area(shape));
    }
}
PatternMeaning
4matches the exact value 4
2 | 3matches 2 or 3
4..=5matches an inclusive range from 4 to 5
_catch-all, matches anything not covered above
Some(x)matches the Some variant and binds its inner value to x

Match Guards and Expression Position

match with guards, used as an expression

fn describe(n: i32) -> String {
    match n {
        n if n < 0 => String::from("negative"),
        0 => String::from("zero"),
        n if n % 2 == 0 => String::from("positive and even"),
        _ => String::from("positive and odd"),
    }
}

fn main() {
    for n in [-3, 0, 4, 7] {
        println!("{} -> {}", n, describe(n));
    }
}
Note: A match guard (the if condition after a pattern) adds extra logic beyond structural matching, but the compiler doesn't use guards to prove exhaustiveness — you still need a fallback arm even if your guards seem to cover everything.
  • Arms are checked top to bottom, so put more specific patterns before general ones.
  • Use _ to explicitly ignore values you don't need to inspect.
  • match returns a value, so it can be a function's tail expression or the right-hand side of a let binding.
  • Combine several values into one arm with the | operator.

Exercise: Rust Match

What does the Rust compiler require of a `match` expression's patterns?