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:
- Concept Explanation: We’ll introduce the
matchexpression, its syntax, and how it compares toif/else if/else. We’ll cover various types of patterns, including literals, variables, wildcards, ranges, tuples, and structs (briefly). - Live Coding Demonstration: We’ll write code examples to showcase different pattern matching scenarios.
- Tasks: You’ll get exercises to practice writing your own
matchexpressions.
Learning Outcomes: By the end of this session, you will be able to:
- Understand the basic syntax of
matchexpressions. - Use
matchto 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:
-
matchallows you to compare a value against a series of patterns and execute code based on which pattern matches. -
It’s similar to a
switchstatement 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:
matchexpressions 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
matcharms. - Match Guards:
ifconditions 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
- FizzBuzz with
match: Rewrite the FizzBuzz program from a previous session, but use amatchexpression instead ofif/else if/else. - Color Matching: Create an enum called
Colorwith variants likeRed,Green,Blue,Yellow, andOther. Write a function that takes aColoras input and uses amatchexpression to print a descriptive message for each color (e.g., “Red is a primary color”). - Tuple Matching with Coordinates: Write a function that takes a tuple representing a point in 2D space (e.g.,
(i32, i32)) and uses amatchexpression 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)
- Array Matching: Write a function that takes an array of three integers (
[i32; 3]) and uses amatchexpression 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.
- Match Guard Challenge: Write a function that takes two integers,
aandb, and uses amatchexpression with match guards to implement the following logic:- If both
aandbare positive, print “Both positive”. - If
ais positive andbis negative, print “a positive, b negative”. - If
ais negative andbis positive, print “a negative, b positive”. - If both
aandbare negative, print “Both negative”. - If either
aorbis zero, print “At least one is zero”.
- If both
- Multiple Pattern and Binding: Write a function that takes one integers,
a, and uses amatchexpression 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.