RustArena

Step 2 of 7

match: exhaustive by design

A match expression has the shape match value { pattern => expression, ... }. Each pattern => expression line is an arm. Rust tries the arms from top to bottom and evaluates the first one whose pattern fits. Since match is an expression, it has a value, so every arm must produce the same type.

#[derive(Debug)]
enum OrderStatus {
    Pending,
    Paid,
    Shipped,
    Delivered,
}

fn advance(status: OrderStatus) -> OrderStatus {
    match status {
        OrderStatus::Pending => OrderStatus::Paid,
        OrderStatus::Paid => OrderStatus::Shipped,
        OrderStatus::Shipped => OrderStatus::Delivered,
        OrderStatus::Delivered => OrderStatus::Delivered,
    }
}

fn main() {
    let mut status = OrderStatus::Pending;
    for _ in 0..4 {
        print!("{:?} -> ", status);
        status = advance(status);
    }
    println!("{:?}", status);
}
Pending -> Paid -> Shipped -> Delivered -> Delivered

This is a tiny state machine: a value that is always in exactly one state, plus rules for moving between states. Enums and match are the natural way to write one in Rust.

Exhaustiveness

A match must handle every possible value. Forget a variant and the program doesn't compile:

enum Light {
    Red,
    Yellow,
    Green,
}

fn seconds(light: Light) -> u32 {
    match light {
        Light::Red => 30,
        Light::Green => 25,
    }
}

fn main() {
    println!("{}", seconds(Light::Yellow));
}
error[E0004]: non-exhaustive patterns: `Light::Yellow` not covered

This is one of Rust's best features at work. Six months from now someone adds Light::FlashingRed, and the compiler lists every match in the codebase that doesn't handle it yet. In many languages, that change would compile fine and misbehave in production.

The catch-all _

The pattern _ matches anything. Put it last to mean "every other value". You need it when the possible values can't all be listed, like numbers, characters or strings. A plain name instead of _ also matches anything, and gives you the value:

fn respond(command: &str) -> String {
    match command {
        "start" => String::from("starting engine"),
        "stop" => String::from("stopping"),
        other => format!("unknown command: {}", other),
    }
}

fn dice_word(n: u32) -> &'static str {
    match n {
        1 => "one",
        6 => "six!",
        _ => "something in between",
    }
}

fn main() {
    println!("{}", respond("start"));
    println!("{}", respond("jump"));
    println!("{} / {}", dice_word(6), dice_word(3));
}
starting engine
unknown command: jump
six! / something in between

(&'static str means "a string literal baked into the program". The lifetimes module explains the 'static part; for now, read it as "returns a literal".)

For your own enums, prefer listing every variant over _. A _ arm silently swallows variants added later, which switches off exactly the help described above.

Common mistakes

Your task

Write fn next(light: Light) -> Light for a traffic light: red turns green, green turns yellow, yellow turns red. List every variant: no _.

Challenge 2

Write fn shortcut(key: char) -> String for a text editor: 's' gives "save", 'o' gives "open", 'q' gives "quit", and any other key gives "no shortcut for X" with the key in place of X.

Challenge 3

Write the state machine of a vending machine: fn on_event(state: Vend, event: &str) -> Vend.

Match on the state, and decide on the event inside each arm (with if or a second match).

Previous: Enums: one of a fixed set of variants Next: Variants that carry data