Web Analytics

Pattern Matching with match

Beginner ~25 min read

Pattern matching is one of Rust's most powerful features. The match expression allows you to compare a value against a series of patterns and execute code based on which pattern matches. Unlike if, match is exhaustive - you must handle all possible cases.

Pattern Matching Flow with match

Basic match Syntax

The match expression compares a value against patterns:

match value {
    pattern1 => expression1,
    pattern2 => expression2,
    pattern3 => expression3,
}
Output
Click Run to execute your code
match is an expression: Like if, match returns a value. All arms must return the same type.

Exhaustive Matching

Rust requires that match expressions handle all possible values:

let number = 5;

match number {
    1 => println!("One"),
    2 => println!("Two"),
    _ => println!("Something else"),  // Catch-all required!
}
Compiler enforces exhaustiveness: If you don't cover all cases, your code won't compile. Use _ (underscore) as a catch-all pattern for remaining cases.
Pattern Description Example
Literal Match exact value 1 => ...
Multiple Match any of several 1 | 2 | 3 => ...
Range Match a range 1..=10 => ...
Variable Bind to variable x => ...
Wildcard Match anything _ => ...

Destructuring Patterns

You can destructure tuples, structs, and enums in match patterns:

Destructuring Tuples

let point = (0, 5);

match point {
    (0, 0) => println!("Origin"),
    (0, y) => println!("On Y-axis at {}", y),
    (x, 0) => println!("On X-axis at {}", x),
    (x, y) => println!("Point at ({}, {})", x, y),
}

Matching Option<T>

Pattern matching is perfect for handling Option types:

let some_number = Some(5);

match some_number {
    Some(n) => println!("Got a number: {}", n),
    None => println!("Got nothing"),
}
Best Practice: Use match instead of unwrap() when handling Option and Result types. It's safer and more explicit.

Match Guards

Add extra conditions to patterns using if:

let number = 4;

match number {
    n if n < 0 => println!("Negative: {}", n),
    n if n == 0 => println!("Zero"),
    n if n % 2 == 0 => println!("Positive even: {}", n),
    n => println!("Positive odd: {}", n),
}
Match guards: The if condition is evaluated after the pattern matches. If the guard is false, matching continues to the next arm.

@ Bindings

Use @ to bind a value to a variable while also testing it:

let age = 25;

match age {
    n @ 0..=12 => println!("Child of age {}", n),
    n @ 13..=19 => println!("Teenager of age {}", n),
    n @ 20..=64 => println!("Adult of age {}", n),
    n @ 65.. => println!("Senior of age {}", n),
}
Output
Click Run to execute your code

Ignoring Values

Using _ to Ignore Values

let triple = (5, 10, 15);

match triple {
    (first, _, third) => {
        println!("First: {}, Third: {}", first, third);
        // Second value is ignored
    }
}

Using .. to Ignore Remaining Parts

let numbers = (1, 2, 3, 4, 5);

match numbers {
    (first, .., last) => {
        println!("First: {}, Last: {}", first, last);
    }
}

Common Patterns

1. Simple State Machine

let state = "running";

match state {
    "idle" => println!("System is idle"),
    "running" => println!("System is running"),
    "stopped" => println!("System is stopped"),
    _ => println!("Unknown state"),
}

2. Error Handling with Result

let result: Result = Ok(42);

match result {
    Ok(value) => println!("Success: {}", value),
    Err(error) => println!("Error: {}", error),
}

3. Multiple Conditions

let pair = (2, -5);

match pair {
    (x, y) if x == y => println!("Equal"),
    (x, y) if x > y => println!("{} is greater", x),
    (x, y) => println!("{} is greater", y),
}

Common Mistakes

1. Non-exhaustive match

Wrong:

let number = 5;
match number {
    1 => println!("One"),
    2 => println!("Two"),
    // Error: non-exhaustive patterns
}

Correct:

let number = 5;
match number {
    1 => println!("One"),
    2 => println!("Two"),
    _ => println!("Other"),  // Catch-all
}

2. Type mismatch in arms

Wrong:

let result = match number {
    1 => "one",
    2 => 2,  // Error: type mismatch
    _ => "other",
};

Correct:

let result = match number {
    1 => "one",
    2 => "two",  // All arms return &str
    _ => "other",
};

3. Unreachable patterns

Wrong:

match number {
    _ => println!("Anything"),
    1 => println!("One"),  // Warning: unreachable!
}

Correct:

match number {
    1 => println!("One"),
    _ => println!("Anything"),  // Catch-all goes last
}

Best Practices

  • Prefer match over if chains: When you have multiple conditions, match is often clearer
  • Order patterns from specific to general: Put more specific patterns first
  • Use meaningful variable names: Instead of x, use descriptive names in patterns
  • Leverage exhaustiveness: Let the compiler help you catch missing cases
  • Use guards sparingly: Complex guards can make code hard to read

Exercise: Pattern Matching Practice

Task: Complete the pattern matching exercises.

Requirements:

  • Add catch-all patterns
  • Use match expressions
  • Destructure tuples
  • Handle Option types
  • Use match guards
  • Apply @ bindings
Output
Click Run to execute your code
Show Solution
fn main() {
    // Fixed with catch-all
    let number = 10;
    match number {
        1 => println!("One"),
        2 => println!("Two"),
        3 => println!("Three"),
        _ => println!("Other"),
    }
    
    // Grade categorization
    let score = 85;
    let grade = match score {
        90..=100 => "A",
        80..=89 => "B",
        70..=79 => "C",
        60..=69 => "D",
        _ => "F",
    };
    println!("Grade: {}", grade);
    
    // Quadrant determination
    let point = (3, -5);
    match point {
        (x, y) if x > 0 && y > 0 => println!("Quadrant I"),
        (x, y) if x < 0 && y > 0 => println!("Quadrant II"),
        (x, y) if x < 0 && y < 0 => println!("Quadrant III"),
        (x, y) if x > 0 && y < 0 => println!("Quadrant IV"),
        _ => println!("On axis"),
    }
    
    // Option handling
    let maybe_number: Option = Some(42);
    let value = match maybe_number {
        Some(n) => n,
        None => 0,
    };
    println!("Value: {}", value);
    
    // Temperature categorization
    let temp = 25;
    match temp {
        t if t < 0 => println!("Freezing"),
        t if t <= 15 => println!("Cold"),
        t if t <= 25 => println!("Comfortable"),
        _ => println!("Hot"),
    }
    
    // Destructure with ..
    let data = (10, 20, 30, 40, 50);
    match data {
        (first, .., last) => {
            println!("First: {}, Last: {}", first, last);
        }
    }
    
    // @ binding
    let age = 17;
    match age {
        n @ 0..=12 => println!("Child: {}", n),
        n @ 13..=19 => println!("Teen: {}", n),
        n @ 20..=64 => println!("Adult: {}", n),
        n @ 65.. => println!("Senior: {}", n),
    }
    
    // Multiple patterns
    let day = 6;
    match day {
        1..=5 => println!("Weekday"),
        6 | 7 => println!("Weekend"),
        _ => println!("Invalid day"),
    }
    
    // Calculator
    let operation = '+';
    let a = 10;
    let b = 5;
    let result = match operation {
        '+' => a + b,
        '-' => a - b,
        '*' => a * b,
        '/' => a / b,
        _ => 0,
    };
    println!("{} {} {} = {}", a, operation, b, result);
}

Summary

  • match is an expression that returns a value
  • Exhaustive: Must handle all possible cases
  • Use _ as a catch-all pattern
  • Patterns can be literals, ranges, or variables
  • Combine multiple patterns with |
  • Destructuring: Extract values from tuples, structs, enums
  • Match guards: Add conditions with if
  • @ bindings: Bind and test values simultaneously
  • Use _ to ignore values, .. to ignore remaining
  • All arms must return the same type
  • More specific patterns should come before general ones

What's Next?

Congratulations! You've completed Module 3: Control Flow. You now understand if expressions, loops, and pattern matching - the building blocks of program logic in Rust.

In the next module, we'll dive into Ownership - the feature that makes Rust unique. You'll learn about ownership rules, borrowing, references, and how Rust achieves memory safety without a garbage collector.