Rust Fundamentals

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.

Loading Exercise...

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 f64 here)
  • 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 if expression is part of a let statement

Note: Rust has no ternary operator (condition ? value1 : value2) because if expressions serve the same purpose and are more readable.

Loading Exercise...

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:


Loading Exercise...

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..6 is a range from 1 to 5 (excludes 6)
  • 1..=5 is a range from 1 to 5 (includes 5)

You can iterate in reverse using .rev():

Note: for loops are preferred over while loops for iteration because they’re safer. With a for loop, you can’t accidentally create an infinite loop or access an array out of bounds.

Loading Exercise...

Loop 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.

Loading Exercise...

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):


Loading Exercise...

Matching Ranges

You can match ranges of values:

Note: The &'static lifetime 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.

Loading Exercise...

Match Guards

You can add if conditions to match arms:


Loading Exercise...

Matching 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 Some and None for now - we’ll look into the Option type in the next chapter. The key point is that if let is useful when you only care about one pattern.

Loading Exercise...

If Let with Else

You can combine if let with else:


Loading Exercise...

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

Loading Exercise...

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.

Loading Exercise...

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)


Loading Exercise...

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: loop for infinite loops, while for conditional loops, and for for 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 with if
  • 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.