blog.dopana

Back

Rust has an extremely powerful control flow construct called match. It allows you to compare a value against a series of patterns and execute code based on which pattern matches first.

Explain Like I’m 10: The Coin-Sorting Machine#

Think of match like a coin-sorting machine:

  1. You drop a coin into the top slot.
  2. The coin rolls down tracks with holes of various sizes.
  3. When it encounters the first hole that fits its exact dimensions, it falls through into that specific bin.
  4. Each coin triggers an action only for the bin it falls into.
graph TD
    Input["Input Value"] --> Arm1{"Matches Pattern 1?"}
    Arm1 -- Yes --> Action1["Execute Arm 1 & Return Result"]
    Arm1 -- No --> Arm2{"Matches Pattern 2?"}
    Arm2 -- Yes --> Action2["Execute Arm 2 & Return Result"]
    Arm2 -- No --> ArmCatch{"Matches Catch-all (_ / other)?"}
    ArmCatch -- Yes --> ActionCatch["Execute Default Arm"]

In Rust, the compiler makes this even safer: it forces you to handle every possible coin type (exhaustiveness checking), ensuring your program never crashes on unexpected inputs.

Basic match Syntax#

A match expression is composed of match arms. An arm has two parts: a pattern to match against and the code to run when matched, separated by the => operator.

[!NOTE] match is an expression in Rust, meaning it returns a value. All match arms must return values of the same type.

Patterns that Bind to Values#

Match arms can bind to the inner values stored inside enum variants, making it easy to extract and inspect data:

When passing Coin::Quarter(UsState::Alaska), the state variable binds to UsState::Alaska in that match arm.

Matching with Option<T>#

Rust replaces null with Option<T> (Some(T) or None). Using match is the canonical way to safely unpack and transform Option values:

src/main.rs
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);

    println!("five + 1 = {six:?}");
    println!("none + 1 = {none:?}");
}
rust

Matches Are Exhaustive & Catch-all Patterns#

Rust requires match arms to be exhaustive: all possibilities must be accounted for. Missing a variant causes a compilation error:

src/main.rs
// Compile error: pattern `None` not covered!
fn plus_one_invalid(x: Option<i32>) -> Option<i32> {
    match x {
        Some(i) => Some(i + 1),
    }
}
rust

Using Catch-all Variables and _#

When you only care about specific cases and want a default action for everything else:

[!TIP] Catch-all patterns (other or _) must always be placed at the very end of the match block, as arms are evaluated in order.

Summary#

  • match compares a value against patterns sequentially and runs the first matching arm.
  • Arms can destructure and bind to data inside enum variants.
  • The compiler guarantees exhaustiveness, eliminating unhandled edge cases.
  • Use _ or variable catch-alls at the end when you don’t need to list every specific case.

References#