Pattern Matching

1. Welcome and Icebreaker:

Welcome back!

If you could design a real-world object that uses pattern matching to respond to different inputs or situations, what would it be and how would it work?

2. Outline the Session:

Here’s our plan:

  1. Concept Explanation: We’ll introduce the match expression, its syntax, and how it compares to if/else if/else. We’ll cover various types of patterns, including literals, variables, wildcards, ranges, tuples, and structs (briefly).
  2. Live Coding Demonstration: We’ll write code examples to showcase different pattern matching scenarios.
  3. Tasks: You’ll get exercises to practice writing your own match expressions.

Learning Outcomes: By the end of this session, you will be able to:

  • Understand the basic syntax of match expressions.
  • Use match to compare a value against multiple patterns.
  • Use various types of patterns (literals, variables, wildcards, ranges).
  • Destructure tuples and arrays using match.
  • Use match guards for more complex conditions.
  • Understand the exhaustiveness requirement of match.

Expectations: Pattern matching can seem complex at first, but it becomes very intuitive with practice. Don’t hesitate to ask questions!

3. Concept Explanation:

The match Expression:

  • match allows you to compare a value against a series of patterns and execute code based on which pattern matches.

  • It’s similar to a switch statement in other languages, but much more powerful and flexible.

  • Syntax:

    match value {
        pattern1 => expression1,
        pattern2 => expression2,
        pattern3 => {
            // Multiple lines of code
            expression3
        },
        _ => expression4, // Catch-all pattern (wildcard)
    }
    
  • Exhaustiveness: match expressions in Rust must be exhaustive. This means that you must cover all possible values of the matched expression. The wildcard pattern (_) is often used as a catch-all to ensure exhaustiveness.

Types of Patterns:

  • Literals: Match against specific values (numbers, characters, booleans, string literals).

    let x = 1;
    match x {
        0 => println!("zero"),
        1 => println!("one"),
        2 => println!("two"),
        _ => println!("other"),
    }
    
  • Variables: Bind the matched value to a variable name.

    let x = 5;
    match x {
        y => println!("Matched: {}", y), // y will be bound to 5
    }
    
  • Wildcard (_): Matches any value, but doesn’t bind it to a variable.

    match x {
        0 => println!("zero"),
        _ => println!("not zero"), // Matches anything else
    }
    
  • Ranges: Match against a range of values (inclusive). This works with numeric and character types.

    let x = 3;
    match x {
        0..=5 => println!("0 to 5"),
        6..=10 => println!("6 to 10"),
        _ => println!("outside range"),
    }
    
  • Tuples: Destructure and match against tuple elements.

    let tup = (1, "hello");
    match tup {
        (0, _) => println!("First element is 0"),
        (x, "world") => println!("Second element is 'world', first is {}", x),
        (a, b) => println!("Other tuple: {}, {}", a, b),
    }
    
  • Arrays: Destructure and match against array elements.

    let arr = [1, 2, 3];
    match arr {
        [1, _, _] => println!("Starts with 1"),
        [a, b, c] => println!("Other array {} {} {}",a,b,c),
    }
    
  • Match Guards: Add extra conditions to a pattern using if.

    let x = 4;
    let y = false;
    match x {
        n if n > 5 => println!("Greater than 5: {}", n),
        n if y => println!("y is true: {}", n), // Only matches if y is true
        n => println!("Other: {}", n),
    }
    
  • Multiple Patterns: Use | to match against multiple patterns in a single arm.

        let x = 1;
    
        match x {
            1 | 2 => println!("one or two"),
            3 => println!("three"),
            _ => println!("anything"),
        }
    
  • @ Bindings: @ lets us create a variable at the same time we’re testing a pattern.

        let x = 4;
    
        match x {
            n @ 1..=5 => println!("x is in the range 1-5: {}", n),
            _ => println!("x is something else"),
        }
    

4. Live Coding Demonstration:

fn main() {
    // Literal matching
    let number = 2;
    match number {
        1 => println!("One"),
        2 => println!("Two"),
        3 => println!("Three"),
        _ => println!("Other"),
    }

    // Variable binding
    let value = 7;
    match value {
        x => println!("Matched: {}", x), // x is bound to 7
    }

    // Wildcard
    let option = Some(5);
    match option {
        Some(x) => println!("Some: {}", x),
        None => println!("None"), // No wildcard needed because Option has only two variants
    }

    // Range
    let grade = 85;
    match grade {
        90..=100 => println!("A"),
        80..=89 => println!("B"),
        70..=79 => println!("C"),
        0..=69 => println!("F"),
        _ => println!("Invalid grade!"), // Handle grades outside 0-100
    }

    // Tuple destructuring
    let point = (3, -2);
    match point {
        (0, 0) => println!("Origin"),
        (x, 0) => println!("On x-axis: ({}, 0)", x),
        (0, y) => println!("On y-axis: (0, {})", y),
        (x, y) => println!("({}, {})", x, y),
    }

      // Array destructuring
    let arr = [10,20,30];
    match arr {
        [0, _, _] => println!("First element zero"),
        [x, y, z] => println!("Array: {} {} {}",x,y,z),
    }
    // Match guard
    let temperature = 25;
    let is_sunny = true;
    match temperature {
        t if t > 30 && is_sunny => println!("Hot and sunny!"),
        t if t > 30 && !is_sunny => println!("Hot but not sunny."),
        t if t < 10 => println!("Cold"),
        _ => println!("Moderate"),
    }
    // Multiple Patterns
     let x = 4;
    match x {
        1 | 2 => println!("one or two"),
        3 => println!("three"),
        _ => println!("other"),
    }

    // @ Bindings
     let x = 3;
    match x {
        n @ 1..=5 => println!("Found in range: {}", n),
        _ => println!("Not in range"),
    }
}

Key Takeaways:

  • Exhaustiveness: The examples demonstrate how to ensure that all cases are covered.
  • Pattern Variety: We show different types of patterns in action.
  • Destructuring: Tuples and arrays can be neatly unpacked within match arms.
  • Match Guards: if conditions add flexibility to patterns.
  • Multiple Patterns: | using for multiple patterns check.
  • @ Bindings: @ create a variable at the same time we’re testing a pattern.

5. Tasks

  1. FizzBuzz with match: Rewrite the FizzBuzz program from a previous session, but use a match expression instead of if/else if/else.
  2. Color Matching: Create an enum called Color with variants like Red, Green, Blue, Yellow, and Other. Write a function that takes a Color as input and uses a match expression to print a descriptive message for each color (e.g., “Red is a primary color”).
  3. Tuple Matching with Coordinates: Write a function that takes a tuple representing a point in 2D space (e.g., (i32, i32)) and uses a match expression to determine which quadrant the point lies in:
    • Quadrant I: (x > 0, y > 0)
    • Quadrant II: (x < 0, y > 0)
    • Quadrant III: (x < 0, y < 0)
    • Quadrant IV: (x > 0, y < 0)
    • On x-axis: (y == 0)
    • On y-axis: (x == 0)
    • Origin: (x == 0, y == 0)
  4. Array Matching: Write a function that takes an array of three integers ([i32; 3]) and uses a match expression to:
    • Print “Starts with zero” if the first element is 0.
    • Print “Ends with 100” if the last element is 100.
    • Print “Sum is even” if the sum of all elements is even.
    • Print “Sum is odd” if the sum of all elements is odd.
  5. Match Guard Challenge: Write a function that takes two integers, a and b, and uses a match expression with match guards to implement the following logic:
    • If both a and b are positive, print “Both positive”.
    • If a is positive and b is negative, print “a positive, b negative”.
    • If a is negative and b is positive, print “a negative, b positive”.
    • If both a and b are negative, print “Both negative”.
    • If either a or b is zero, print “At least one is zero”.
  6. Multiple Pattern and Binding: Write a function that takes one integers, a, and uses a match expression with multiple pattern and binding to implement the following logic:
    • If a in range 10 to 20 (included) print this value
    • if a equals 1 , 2 ,3 print this number.
    • other wise print other.