Control Flow and Pattern Matching
Learning Objectives
- You can use if expressions to make decisions in your code.
- You understand the different types of loops in Rust and when to use each.
- You can use match expressions for powerful pattern matching.
- You know when to use if let as a shortcut for simple patterns.
- You can build a complete program using control flow structures.
Warming up
Here is a set of tiny programming tasks to get you familiar with syntax that relates to control flow and pattern matching. You can solve these exercises directly in the embedded code editor.
If Expressions
We’ve already seen if in previous examples, but let’s look at it more carefully. In Rust, if is an expression, not just a statement. This means it returns a value.
Basic If/Else
Here’s the basic form:
The condition in an if must be a boolean (true or false). Unlike some languages, Rust will not automatically convert numbers to booleans:
let number = 3;if number { // Error! Expected bool, found integer println!("This won't work");}You must explicitly compare:
If as an Expression
Since if is an expression, you can use it to assign values:
Notice a few important things:
- Both branches must return the same type (both are
f64here) - We don’t use semicolons after the values in each branch (they’re expressions, not statements)
- We do need a semicolon after the closing brace because the entire
ifexpression is part of aletstatement
Note: Rust has no ternary operator (
condition ? value1 : value2) becauseifexpressions serve the same purpose and are more readable.
If Expression Requirements
0 / 3 points
Which of the following statements about if expressions in Rust are true?
Loops
Rust has three types of loops: loop, while, and for. Each serves a different purpose.
The loop Keyword
The loop keyword creates an infinite loop. You exit it with break:
You can also return values from loops using break:
Returning Values from Loops
0 / 3 points
What will this code print?
let mut counter = 0;let result = loop { counter += 1; if counter == 5 { break counter * 3; }};println!("{}", result);While Loops
Rust has also while loops for condition-based looping. However, in most cases, for loops are preferred for iteration because they’re safer and more concise. Here’s a quick example of a while loop:
fn main() { let mut cups = 1;
while cups <= 5 { println!("Cup #{}", cups); cups += 1; }}For Loops
The for loop is used to iterate over collections or ranges. This is the loop you’ll use most often:
Note the range syntax:
1..6is a range from 1 to 5 (excludes 6)1..=5is a range from 1 to 5 (includes 5)
You can iterate in reverse using .rev():
Note:
forloops are preferred overwhileloops for iteration because they’re safer. With aforloop, you can’t accidentally create an infinite loop or access an array out of bounds.
Range Syntax
0 / 3 points
What is the difference between these two ranges?
for i in 1..5 { } // Range 1for i in 1..=5 { } // Range 2Loop Labels
When you have nested loops, you can label them and break or continue to a specific loop:
Loop labels start with a single quote (') and are useful when you need to break out of nested loops.
Loop Labels
0 / 3 points
What is the purpose of loop labels like 'outer in this code?
'outer: for x in 1..5 { for y in 1..5 { if y == 3 { break 'outer; } }}Match Expression
The match expression is one of Rust’s most powerful features. It allows you to compare a value against a series of patterns and execute code based on which pattern matches.
Basic Match
Let’s start with a simple example:
The _ is a wildcard that matches anything. Think of it as the “default” case.
Note: Match expressions must be exhaustive - they must cover all possible values. If you remove the
_pattern and the value is 4, the code won’t compile!
Match as an Expression
Like if, match is an expression and returns a value:
Matching Multiple Patterns
You can match multiple values with | (or):
Match with Multiple Patterns
0 / 3 points
What does this match expression evaluate to when day is 6?
let day = 6;let day_type = match day { 1 | 2 | 3 | 4 | 5 => "weekday", 6 | 7 => "weekend", _ => "invalid",};Matching Ranges
You can match ranges of values:
Note: The
&'staticlifetime indicates that the returned string slice has a static lifetime, meaning it lives for the entire duration of the program. This is used as we’re returning string literals.
Grade Calculator
0 / 10 points
Write a function calculate_grade that takes an integer score (0-100) as input and returns the corresponding number grade. Use pattern matching to determine the grade based on the following scale:
- 90-100: 5
- 85-89: 4
- 80-84: 3
- 75-79: 2
- 70-74: 1
For other scores, return 0.
Function call example:
let score = 87;let grade = calculate_grade(score);println!("The grade for score {} is {}", score, grade); // prints The grade for score 87 is 4Match Guards
You can add if conditions to match arms:
FizzBuzz
0 / 15 points
Write a function fizzbuzz that takes an integer as an input and returns a string according to the following rules:
- If the number is divisible by 3, return “Fizz”.
- If the number is divisible by 5, return “Buzz”.
- If the number is divisible by both 3 and 5, return “FizzBuzz
- Otherwise, return the number as a string.
Hint! Numbers have a to_string() method that can be used to convert them to strings. Alternatively, you can use the format! macro to convert a number to a string, e.g., format!("{}", number). Similarly, you can use the modulus operator % to check for divisibility.
Function call examples:
println!("{}", fizzbuzz(15)); // prints FizzBuzzprintln!("{}", fizzbuzz(9)); // prints Fizzprintln!("{}", fizzbuzz(10)); // prints Buzzprintln!("{}", fizzbuzz(7)); // prints 7Matching Strings
You can match on strings and string slices:
If Let: A Shortcut
Sometimes you only care about one specific pattern and want to ignore all others. The if let syntax provides a convenient shortcut:
Note: Don’t worry about
SomeandNonefor now - we’ll look into theOptiontype in the next chapter. The key point is thatif letis useful when you only care about one pattern.
If Let vs Match
0 / 3 points
When should you use if let instead of match?
If Let with Else
You can combine if let with else:
If Let Syntax
0 / 3 points
What does this code do?
let some_value = Some(7);if let Some(x) = some_value { println!("Got: {}", x);}When to Use If Let vs Match
Use if let when:
- You only care about one pattern
- You want more concise code for simple cases
Use match when:
- You need to handle multiple patterns
- You want the compiler to check exhaustiveness
- Your logic is complex
Match Expression Type Consistency
0 / 3 points
Will this code compile? Why or why not?
let x = 5;let result = match x { 1 => "one", 2 => "two", _ => 0,};Example: Coffee Shop Menu System
Let’s build a complete menu system that demonstrates all the control flow concepts we’ve learned:
Let’s break down the key control flow elements in this program:
1. Main Loop
The program uses an infinite loop that only exits when the user chooses to quit:
loop { display_menu(); let input = read_number("Enter choice: "); // ... process input
if user_wants_to_quit { break; }}2. Menu Choice Handling
The main menu logic uses match with ranges and specific values:
match choice { 0 => /* Exit */, 1..=4 => /* Add drink */, _ => /* Invalid choice */,}3. Utility Functions
Functions like get_drink_name and get_drink_price use match to return values based on the drink choice. This keeps the main logic clean and focused.
Expanded Coffee Menu System
0 / 20 points
The assignment template comes with the above coffee shop system.
First, modify the system so that the user can add multiple counts of a coffee to their order. Implement this so that after the user selects a coffee type (1-4), the program prompts the user to enter the quantity of that coffee they wish to add to their order. The program should then update the total cost and item count accordingly based on the selected coffee’s price and the specified quantity. If the count is zero or negative, the program should display an error message and not update the order.
Second, add a new menu option (5) that allows the user to clear their current order. When this option is selected, the program should reset the total cost and item count to zero and display a message indicating that the order has been cleared.
Third, modify the program so that when the user chooses to exit (option 0), it only displays the order summary (total items and total cost) if there are items in the order.
Common Patterns and Idioms
Let’s look at some common patterns you’ll use frequently:
Looping with Index
When you need both the index and the value:
Early Return Pattern
Exit early when conditions aren’t met:
Counting with Conditions
Finding Maximum (and Minimum)
Finding Values
0 / 20 points
Write the following four functions that find the minimum, maximum, sum, and average values in a list of integers.
fn find_minimum(arr: &[i32]) -> i32: Takes an array of integers and returns the minimum value. If the array is empty, return-1.fn find_maximum(arr: &[i32]) -> i32: Takes an array of integers and returns the maximum value. If the array is empty, return-1.fn calculate_sum(arr: &[i32]) -> i32: Takes an array of integers and returns the sum of all values. If the array is empty, return0.fn calculate_average(arr: &[i32]) -> f64: Takes an array of integers and returns the average value as a floating-point number. If the array is empty, return-1.0.
Function call examples:
let numbers = [3, 5, 1, 8, 2];println!("Minimum: {}", find_minimum(&numbers)); // prints Minimum: 1println!("Maximum: {}", find_maximum(&numbers)); // prints Maximum: 8println!("Sum: {}", calculate_sum(&numbers)); // prints Sum: 19println!("Average: {}", calculate_average(&numbers)); // prints Average: 3.8Hint! You can use the method is_empty() to check if an array is empty.
Summary
In this chapter, we explored Rust’s control flow structures:
- If expressions can be used as statements or expressions to return values
- Rust has no ternary operator because if expressions serve the same purpose
- Three types of loops:
loopfor infinite loops,whilefor conditional loops, andforfor iteration - For loops are preferred for iteration because they’re safer than while loops
- Match expressions provide powerful pattern matching with exhaustiveness checking
- Match can use: ranges, multiple patterns with
|, and guards withif - If let provides a convenient shortcut when you only care about one pattern
- All match arms must return the same type when match is used as an expression
The match expression is particularly powerful, enabling concise and clear handling of complex branching logic.