Loading...
Loading...

Mastering Pattern Matching in Rust

Pattern Matching is Rust's powerful control flow construct that allows you to compare values against patterns and execute code based on which pattern matches. It provides exhaustive checking (ensuring all cases are handled), destructuring capabilities, and works seamlessly with enums, structs, and primitive types.

1. Basic Match Expressions

The match expression is Rust's most comprehensive pattern matching tool. It compares a value against a series of patterns and executes the code of the first matching arm.

fn main() {
    let number = 3;
    
    match number {
        1 => println!("One"),  // Exact value matching
        2 | 3 | 5 | 7 => println!("Prime"),  // Multiple values
        4 | 6 | 8 | 9 => println!("Composite"),
        _ => println!("Out of range"), // Default catch-all
    }
    
    // Match can return values
    let description = match number {
        1 => "unity",
        2 => "binary",
        _ => "other",  // Must cover all possibilities
    };
}

Match Quiz

What happens if you remove the `_` case in this match?

match some_u8_value {
    0 => "zero",
    1 => "one",
}
  • It defaults to the first arm
  • Compiler error: non-exhaustive patterns
  • Runtime panic when unmatched

Key Points: The _ pattern is required when not all cases are explicitly handled. Match arms are checked in order from top to bottom. Match expressions must be exhaustive for enums.

2. Destructuring Values

Destructuring allows you to break down complex data types into their components. Rust can destructure tuples, structs, enums, and references.

// Destructuring tuples
let pair = (0, -2);
match pair {
    (0, y) => println!("First is 0, y = {}", y),  // Match first element
    (x, 0) => println!("x = {}, second is 0", x),
    _ => println!("No zeros"),  // Catch remaining cases
}

// Destructuring structs
struct Point { x: i32, y: i32 }
let p = Point { x: 0, y: 7 };

match p {
    Point { x, y: 0 } => println!("On x axis at {}", x),
    Point { x: 0, y } => println!("On y axis at {}", y),
    Point { x, y } => println!("At ({}, {})", x, y),  // Full destructure
}

Destructuring Quiz

What does this match arm do?

Point { x: a @ 0..=5, y } => println!("Special x: {}", a)
  • Matches any Point
  • Matches Points where x is 0-5 and binds x to 'a'
  • Only matches if y is also 0-5

Destructuring Tips: Use @ to both match and bind a value. The .. pattern ignores remaining fields in structs. Nested destructuring works for complex types.

3. Matching Enums

Enums and match are a perfect combination in Rust. The compiler ensures you handle all possible variants, making your code more robust.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn process_message(msg: Message) {
    match msg {
        Message::Quit => println!("Quit"),  // Unit variant
        Message::Move { x, y } => {  // Struct-like variant
            println!("Move to x: {}, y: {}", x, y)
        },
        Message::Write(text) => println!("Text: {}", text),  // Tuple variant
        Message::ChangeColor(r, g, b) => {  // Tuple variant
            println!("Color: {}, {}, {}", r, g, b)
        },
    }
}

Enum Quiz

What's the advantage of matching enums vs using if-let chains?

  • No advantage, just syntax preference
  • Compiler ensures all variants are handled
  • Enums can only be matched, not if-let

Enum Matching Benefits: Exhaustiveness checking prevents bugs. You can access inner data directly in match arms. Complex enums can be nested and matched recursively.

4. Pattern Guards

Pattern guards add extra conditions to match arms using if expressions. They let you express more complex logic than patterns alone.

let num = Some(4);

match num {
    Some(x) if x < 5 => println!("Less than 5: {}", x),  // With condition
    Some(x) => println!("{}", x),  // Catch remaining Some values
    None => (),  // Handle None case
}

// Combining guards with patterns
match some_value {
    (x, y) if x == y => println!("Equal"),  // Check equality
    (x, y) if x + y == 0 => println!("Additive inverse"),
    _ => println!("No special case"),  // Default
}

Guard Quiz

When would you use a pattern guard instead of nested matching?

  • When you need to match more than 3 patterns
  • When you need additional conditions beyond pattern structure
  • Only when working with numbers

Guard Usage: Guards are evaluated after pattern matching. They can reference bound variables from the pattern. Complex conditions often benefit from guards.

5. Advanced Patterns

Advanced pattern matching includes range matching, binding subpatterns, and ignoring values. These techniques make patterns more expressive.

// @ bindings combine matching and binding
match age {
    age @ 0..=12 => println!("Child (age {})", age),  // Bind and match
    age @ 13..=19 => println!("Teen (age {})", age),
    age => println!("Adult (age {})", age),
}

// Matching ranges (inclusive on both ends)
match number {
    1..=5 => println!("1-5"),  // Inclusive range
    6..=10 => println!("6-10"),
    _ => println!("Other"),
}

// Ignoring parts with _
let triple = (1, -2, 3);
match triple {
    (x, _, z) => println!("First: {}, Last: {}", x, z),  // Ignore middle
}

Advanced Quiz

What does `_` signify in patterns?

  • A variable that must be used
  • A placeholder to ignore values
  • A wildcard that matches everything

Advanced Features: .. ignores remaining fields in structs. | matches multiple patterns in one arm. Ranges work with chars and integers.

6. Concise Matching with if-let/while-let

if-let and while-let provide more concise syntax for cases where you only care about one pattern. They're syntactic sugar for match expressions.

// if-let for single pattern matching
let config_max = Some(3u8);
if let Some(max) = config_max {  // Extract and match
    println!("Max configured: {}", max);
}

// while-let for conditional loops
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {  // Continue while Some
    println!("{}", top);
}

// Combining with else
if let Some(3) = config_max {
    println!("Three!");
} else {  // Optional else clause
    println!("Not three");
}

if-let Quiz

When should you prefer if-let over match?

  • When you need exhaustive checking
  • When you only care about one pattern
  • When working with integers only

Usage Guidelines: Use if-let for simple optional checks. while-let is great for iterator-like processing. Prefer match when you need exhaustiveness.

0 Interaction
0 Views
Views
0 Likes
×
×
×
🍪 CookieConsent@Ptutorials:~

Welcome to Ptutorials

$ Allow cookies on this site ? (y/n)

top-home