Lesson 10

Pattern Matching

Master match expressions, destructuring, guards, and the if let shorthand.

Match: Rust's Swiss Army Knife

The match expression is one of Rust's most powerful features. It compares a value against a series of patterns and runs the code for the first match. Unlike switch statements in other languages, Rust's match is exhaustive -- you must handle every possible case. The compiler won't let you forget one.

match value {
    pattern1 => expression1,
    pattern2 => expression2,
    _ => default_expression,  // _ matches everything else
}

Destructuring

Patterns can pull apart (destructure) enums, tuples, and structs. This lets you bind the inner values to variables right in the pattern. Combined with match, it makes handling complex data structures elegant and safe.

If Let

When you only care about one pattern, if let is a concise shorthand for match. It's perfect for handling Option or checking a single enum variant without writing a full match block.

Try it

enum Command {
    Quit,
    Echo(String),
    Move { x: i32, y: i32 },
    Color(u8, u8, u8),
}

fn process_command(cmd: &Command) {
    match cmd {
        Command::Quit => println!("  Quitting!"),
        Command::Echo(msg) => println!("  Echo: {}", msg),
        Command::Move { x, y } => println!("  Moving to ({}, {})", x, y),
        Command::Color(r, g, b) => println!("  Color: rgb({}, {}, {})", r, g, b),
    }
}

fn classify_number(n: i32) -> &'static str {
    match n {
        0 => "zero",
        1..=9 => "a single-digit number",
        10..=99 => "a two-digit number",
        100..=999 => "a three-digit number",
        _ => "a big number",
    }
}

fn main() {
    // Match on enums with data
    println!("--- Processing Commands ---");
    let commands = vec![
        Command::Echo(String::from("hello")),
        Command::Move { x: 10, y: 20 },
        Command::Color(255, 128, 0),
        Command::Quit,
    ];

    for cmd in &commands {
        process_command(cmd);
    }

    // Match on numbers with ranges
    println!("\n--- Number Classification ---");
    for n in [0, 5, 42, 500, 10000] {
        println!("  {} is {}", n, classify_number(n));
    }

    // Match with guards
    println!("\n--- Match Guards ---");
    let pair = (2, -3);
    let description = match pair {
        (x, y) if x > 0 && y > 0 => "both positive",
        (x, y) if x < 0 && y < 0 => "both negative",
        (_, 0) | (0, _) => "one is zero",
        _ => "mixed signs",
    };
    println!("  ({}, {}) -> {}", pair.0, pair.1, description);

    // Destructuring tuples
    println!("\n--- Tuple Destructuring ---");
    let point = (3, 7);
    let (x, y) = point;
    println!("  x = {}, y = {}", x, y);

    // if let: concise single-pattern matching
    println!("\n--- if let ---");
    let favorite: Option<&str> = Some("Rust");
    if let Some(lang) = favorite {
        println!("  Favorite language: {}", lang);
    }

    let nothing: Option<i32> = None;
    if let Some(val) = nothing {
        println!("  Found: {}", val);
    } else {
        println!("  Nothing found -- and that's ok!");
    }
}

Challenge

Add a ChangeSpeed(i32) variant to the Command enum and handle it in process_command. Then add a ChangeSpeed command to the commands vector.

Previous: Enums

Next: Error Handling