Pattern Matching Fundamentals

Rust's pattern matching syntax is built around the match expression, which provides exhaustive checking and compile-time guarantees. Patterns can be simple literals, variables, wildcards, or complex destructuring expressions.

enum Color {
    Red,
    Green,
    Blue,
    RGB(u8, u8, u8),
    Hex(String),
}

fn describe_color(color: Color) -> String {
    match color {
        Color::Red => "Red".to_string(),
        Color::Green => "Green".to_string(),
        Color::Blue => "Blue".to_string(),
        Color::RGB(r, g, b) => format!("RGB({}, {}, {})", r, g, b),
        Color::Hex(hex) => format!("Hex: {}", hex),
    }
}

Advanced Pattern Techniques

1. Guard Clauses and Complex Conditions

Guard clauses allow you to add additional conditions to patterns using the if keyword:

fn process_number(n: i32) -> String {
    match n {
        x if x < 0 => "negative".to_string(),
        x if x > 0 && x < 10 => "small positive".to_string(),
        x if x >= 10 && x < 100 => "medium positive".to_string(),
        x if x >= 100 => "large positive".to_string(),
        _ => "zero".to_string(),
    }
}

// More complex guard example
fn analyze_coordinates(x: f64, y: f64) -> String {
    match (x, y) {
        (0.0, 0.0) => "origin".to_string(),
        (x, y) if x.abs() > 100.0 && y.abs() > 100.0 => "far away".to_string(),
        (x, y) if x > 0.0 && y > 0.0 => "first quadrant".to_string(),
        (x, y) if x < 0.0 && y > 0.0 => "second quadrant".to_string(),
        _ => "other".to_string(),
    }
}

2. Destructuring with References and Mutability

Pattern matching works seamlessly with references and mutable bindings, which is crucial for efficient memory management:

struct Point {
    x: i32,
    y: i32,
}

fn process_point_ref(point: &Point) -> i32 {
    match point {
        Point { x, y } => x + y, // Destructuring with references
    }
}

fn modify_point(point: &mut Point) {
    match point {
        Point { x, y } => {
            *x += 10; // Mutable reference binding
            *y += 20;
        }
    }
}

// Using ref pattern to create references
fn analyze_point(point: &Point) -> String {
    match point {
        Point { x: ref x_val, y: ref y_val } => {
            format!("Point at ({}, {})", x_val, y_val)
        }
    }
}

3. Matching with Option and Result Types

Pattern matching shines when working with Rust's error handling types:

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn handle_division(a: f64, b: f64) -> String {
    match divide(a, b) {
        Ok(result) => format!("Result: {}", result),
        Err(error) => format!("Error: {}", error),
    }
}

// Combining with Option
fn find_first_even(numbers: &[i32]) -> Option<i32> {
    numbers.iter().find(|&&n| n % 2 == 0).copied()
}

fn process_even_number(numbers: &[i32]) -> String {
    match find_first_even(numbers) {
        Some(even) => format!("First even number: {}", even),
        None => "No even numbers found".to_string(),
    }
}

4. Advanced Tuple and Struct Patterns

Complex destructuring patterns allow for precise data extraction:

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
    address: Address,
}

#[derive(Debug)]
struct Address {
    street: String,
    city: String,
    zip: String,
}

fn process_person(person: &Person) -> String {
    match person {
        Person { 
            name, 
            age: 0..=17, 
            address: Address { city, .. } 
        } => format!("{} is a minor from {}", name, city),
        Person { 
            name, 
            age: age @ 18..=120, 
            address: Address { street, city, zip } 
        } => format!("{} is {} years old from {}, {} {}", 
                    name, age, street, city, zip),
        Person { name, age, .. } => format!("{} is {} years old", name, age),
    }
}

// Tuple pattern matching
fn analyze_coordinates(coords: (i32, i32, i32)) -> String {
    match coords {
        (x, y, 0) => format!("2D point at ({}, {})", x, y),
        (x, y, z) if z > 0 => format!("3D point at ({}, {}, {})", x, y, z),
        (x, y, z) => format!("3D point with negative Z: ({}, {}, {})", x, y, z),
    }
}

Pattern Matching Performance Considerations

The Rust compiler optimizes pattern matching extensively, but understanding the performance characteristics helps write efficient code:

Pattern TypePerformance CharacteristicsBest Use Case
Literal matchO(1) - direct lookupSimple enum variants
Variable bindingO(1) - referenceExtracting values
Wildcard _O(1) - no operationIgnoring values
Guard clausesO(n) - condition evaluationComplex conditions
Struct destructuringO(1) - field accessComplex data structures
// Efficient pattern matching
fn efficient_match(input: Option<i32>) -> i32 {
    match input {
        Some(value) => value * 2,
        None => 0,
    }
}

// Less efficient due to complex guards
fn inefficient_match(input: i32) -> i32 {
    match input {
        x if x > 100 && x < 200 && x % 2 == 0 => x + 10,
        x if x > 200 && x < 300 && x % 3 == 0 => x + 20,
        _ => 0,
    }
}

Best Practices for Pattern Matching

  1. Use exhaustive matching: Always handle all cases to prevent runtime panics
  2. Combine with if let for simple cases: When you only need one pattern
  3. Prefer match over nested if statements: For better readability
  4. Use @ binding for complex conditions: To capture values while matching
// Good: Exhaustive match
fn exhaustive_match(value: Option<i32>) -> String {
    match value {
        Some(n) => format!("Got value: {}", n),
        None => "No value".to_string(),
    }
}

// Good: Using if let for simple cases
fn simple_match(value: Option<i32>) -> String {
    if let Some(n) = value {
        format!("Got value: {}", n)
    } else {
        "No value".to_string()
    }
}

// Good: Combining patterns with guards
fn complex_pattern(input: &[i32]) -> String {
    match input {
        [] => "Empty".to_string(),
        [first, second] => format!("Two elements: {}, {}", first, second),
        [first, rest @ ..] => format!("First: {}, Rest: {:?}", first, rest),
    }
}

Learn more with useful resources