R
RUSTY YELLOW PAGES

matchKeyword


Explanation

match compares a scrutinee value against a sequence of arms, each written pattern => expression, and runs the first arm whose pattern fits. Arms are separated by commas; the comma after an arm is optional when the arm's expression is itself a { ... } block, since the closing brace already ends the arm. See match expressions for the deeper "why" — when match earns its keep over lighter forms like if let, and how it relates to designing with enums.

Several patterns can share one arm by joining them with |: Some(1) | Some(2) => ... runs the same expression for either shape. The full grammar of or-patterns lives on that page and isn't repeated here.

An arm's pattern can carry an extra condition, called a match guard, written pattern if condition => expression. The guard runs only after the pattern has already matched, and can reference any name the pattern just bound — grammar-wise, it's an arbitrary boolean expression tacked onto an otherwise ordinary arm. See match guards for when reaching for one is the right call versus reshaping the pattern instead.

identifier @ pattern binds identifier to the value being matched while still requiring that value to match pattern — see @ for the full grammar of binding patterns; match arms are the most common place they appear.

match requires its arms to be exhaustive: every value the scrutinee's type could hold must be covered by some arm's pattern, or the code fails to compile. This check only looks at patterns, not guards — an arm with a guard doesn't count as covering its pattern for exhaustiveness purposes, since the guard might evaluate to false at runtime and the compiler can't rule that out. A trailing _ => expression arm (see _) is the usual way to satisfy exhaustiveness when the remaining cases don't need distinct handling. See exhaustiveness checking for what this guarantees and why it matters beyond the grammar rule itself.

Like if, match is an expression: when every arm's expression has the same type and none end with a semicolon, the whole match evaluates to whichever arm ran, and can sit on the right-hand side of a let or as a function's final expression.

Usage examples

Matching on an enum's variants

enum Direction { North, South, East, West }

let heading = Direction::East;

let degrees = match heading { // <- `match` picks the arm whose pattern fits
    Direction::North => 0,
    Direction::South => 180,
    Direction::East => 90,
    Direction::West => 270,
};

println!("{degrees}");

Restriction: every possible value of the scrutinee's type must be covered by some arm's pattern, or this fails to compile — see exhaustiveness checking.

Branching on data (pattern matching)

A thermostat's incoming command needs to both identify its shape and, for the temperature-setting command, confirm the requested value is in a sane range while still keeping the exact number to report back.

enum ThermostatCommand {
    SetTemperature(i32),
    Boost,
    Off,
}

fn describe(cmd: &ThermostatCommand) -> String {
    match cmd { // <- `match` on the command enum
        ThermostatCommand::SetTemperature(t @ 16..=30) => {
            // <- `@` binds `t` to the value while also checking the range
            format!("setting target to {t}C")
        }
        ThermostatCommand::SetTemperature(t) => format!("ignoring out-of-range target {t}C"),
        ThermostatCommand::Boost | ThermostatCommand::Off => "no target change".to_string(),
        // <- `|` lets one arm handle two variants that need identical handling
    }
}

Combining | for variants that share behavior and @ for variants that need both a shape check and the underlying value keeps the dispatch in one match rather than a chain of separate ifs — the Rust Reference on match expressions documents both as first-class parts of an arm's pattern, not add-on sugar.

Handling and propagating errors

Classifying a sensor reading string needs different messages for a parse failure versus a suspiciously cold (but valid) reading — a guard distinguishes the second case from the general success case.

fn classify_reading(raw: &str) -> String {
    match raw.trim().parse::<f64>() {
        Ok(temp) if temp < 0.0 => format!("reading {temp} is below freezing"),
        // <- guard: same `Ok(temp)` shape, extra condition on the value
        Ok(temp) => format!("reading {temp} is nominal"),
        Err(e) => format!("invalid reading {raw:?}: {e}"),
    }
}

println!("{}", classify_reading("-4.5"));

Matching on the Result directly, with a guard for the one Ok sub-case that needs different handling, avoids re-parsing or a second if after the match — the Rust Book covers guards as exactly this kind of value-dependent refinement on an already-matched pattern.

Validating input

Routing a delivery by distance needs every possible u32 value handled, not just the bands anyone thought of in advance — the exhaustiveness requirement is what forces a catch-all here.

fn shipping_zone(distance_km: u32) -> &'static str {
    match distance_km { // <- exhaustive: distance_km could be any u32, so every value needs a home
        0..=10 => "local",
        11..=100 => "regional",
        101..=1000 => "national",
        _ => "international", // <- required: no fixed range of literals ever covers all of u32
    }
}

Because 0..=1000 doesn't come close to covering every u32, the compiler refuses to compile this without a final wildcard — the same exhaustiveness rule that requires every enum variant to appear also requires numeric patterns to visibly account for every value in range, per the Rust Reference on exhaustiveness.

Explanation

Embedded support: Full

match behaves identically under #![no_std] — core-language, allocator- free, compiling to a jump table or comparison chain with no runtime cost beyond that. It's a standard way to decode a protocol byte or a peripheral's status/fault register into one of several known shapes, and the exhaustiveness check is genuinely useful here: it forces every bit pattern the register can hold — including ones that don't correspond to a documented fault — to be given a _ catch-all rather than silently falling through.

Usage examples

Decoding a fault status register

enum Fault { None, OverCurrent, OverTemperature, UnderVoltage }

fn decode_fault(reg: u8) -> Fault {
    match reg { // <- `match` decodes the raw register value into a known set of fault states
        0b000 => Fault::None,
        0b001 => Fault::OverCurrent,
        0b010 => Fault::OverTemperature,
        0b100 => Fault::UnderVoltage,
        _ => Fault::None, // unrecognized bit pattern; treat conservatively
    }
}

Matching an Option from a non-blocking UART read

match uart.read() {
    Some(byte) => process_byte(byte), // <- `match` on the `Option` a non-blocking read returns
    None => {} // nothing available this cycle
}