Enums and Pattern Matching

Matching Multiple Patterns

The or-pattern operator (|) combines two or more patterns into a single pattern (PatternA | PatternB) that succeeds if the scrutinee matches any of the listed alternatives.

Top-Level and Nested Or-Patterns

In Rust, | can be used both at the top level of a match arm and nested inside struct, tuple, slice, reference, or enum variant patterns (Some(1 | 2 | 3)):

enum KeyEvent {
    Char(char),
    Special { code: u16, shift: bool },
}

fn handle_key(event: &KeyEvent) -> &'static str {
    match event {
        // Top-level `|` combining multiple patterns:
        KeyEvent::Char('q' | 'Q') | KeyEvent::Special { code: 27, .. } => "Quit application",

        // Nested `|` inside an enum variant payload:
        KeyEvent::Char('w' | 'a' | 's' | 'd') => "Move player",

        // Nested `|` with ranges:
        KeyEvent::Char('0'..='9' | '+' | '-') => "Numeric input",

        _ => "Unhandled key",
    }
}

fn main() {
    println!("'Q'    -> {}", handle_key(&KeyEvent::Char('Q')));
    println!("Esc    -> {}", handle_key(&KeyEvent::Special { code: 27, shift: false }));
    println!("'w'    -> {}", handle_key(&KeyEvent::Char('w')));
    println!("'7'    -> {}", handle_key(&KeyEvent::Char('7')));
}
'Q'    -> Quit application
Esc    -> Quit application
'w'    -> Move player
'7'    -> Numeric input

Binding Variables Across | Alternatives

When an or-pattern P1 | P2 binds variables, the compiler enforces three strict consistency rules across every alternative separated by |:

  1. Same variable names: Every variable bound in P1 must also be bound in P2 (E0408 if missing).
  2. Same types: Each bound variable must have the exact same type in every alternative (E0308 if mismatched).
  3. Same binding mode: A variable cannot be bound by value in P1 and by reference in P2 (E0409).
enum MetricEvent {
    CpuAlert { host: String, percent: u8 },
    MemAlert { host: String, percent: u8 },
    Heartbeat { host: String },
}

fn summarize(event: &MetricEvent) {
    match event {
        // Both alternatives bind `host: &String` and `percent: &u8`:
        MetricEvent::CpuAlert { host, percent }
        | MetricEvent::MemAlert { host, percent } => {
            println!("ALERT on {host}: usage at {percent}%");
        }
        MetricEvent::Heartbeat { host } => {
            println!("OK heartbeat from {host}");
        }
    }
}

fn main() {
    let e1 = MetricEvent::CpuAlert {
        host: String::from("db-01"),
        percent: 94,
    };
    let e2 = MetricEvent::Heartbeat {
        host: String::from("web-02"),
    };

    summarize(&e1);
    summarize(&e2);
}
ALERT on db-01: usage at 94%
OK heartbeat from web-02

Error: Missing Variable Binding in One Alternative (E0408)

// ❌ does not compile
enum Packet {
    Data(u32),
    Ping,
}

fn inspect(p: Packet) {
    match p {
        Packet::Data(id) | Packet::Ping => println!("{id}"),
    }
}

fn main() {}
error[E0408]: variable `id` is not bound in all patterns
 --> src/main.rs:8:28
  |
8 |         Packet::Data(id) | Packet::Ping => println!("{id}"),
  |                      --    ^^^^^^^^^^^^ pattern doesn't bind `id`
  |                      |
  |                      variable not in all patterns

Operator Precedence of | with @ and Match Guards

  • With @ bindings (x @ (1 | 2 | 3)): The @ operator binds more tightly than |, so wrap the alternatives in parentheses (val @ (10 | 20 | 30)) if you want val to bind across all alternatives without repeating val @ 10 | val @ 20.
  • With if match guards (A | B | C if condition): A match guard if condition at the end of an arm applies to all alternatives A, B, and C in that arm.