Skip to main content

`match` Expressions (Part 2): Advanced Patterns

In the previous article, we learned the basics of match expressions. Now, we're going to explore some of the more advanced patterns that make match one of Rust's most powerful features. These patterns allow you to write even more expressive and concise conditional code.


📚 Prerequisites

Before we begin, you should be familiar with:

  • The basic syntax of match expressions.
  • The Option<T> enum.

🎯 Article Outline: What You'll Master

In this article, you will learn about advanced match patterns:

  • Matching on a range of values.
  • Binding values to variables within a pattern.
  • Adding "match guards" to a pattern.
  • Using @ bindings to bind to a variable while also testing it.

🧠 Section 1: Matching on Ranges

You can match on a range of values using the ..= syntax. This is useful for checking if a value falls within a certain range.

fn main() {
let x = 5;

match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
}

This will print "one through five". This pattern is only available for char and integer types.


💻 Section 2: Binding Values to Variables

In our previous article, we saw how to bind the value inside a Some to a variable:

match Some(5) {
Some(i) => println!("The number is {}", i),
None => (),
}

This is a form of destructuring. You can do this with any enum variant that holds a value. This is incredibly useful for extracting data from your types.


🔬 Section 3: Match Guards

A match guard is an additional if condition that you can add to a match arm. The arm will only be chosen if the pattern matches and the if condition is true.

fn main() {
let num = Some(4);

match num {
Some(x) if x % 2 == 0 => println!("The number {} is even", x),
Some(x) => println!("The number {} is odd", x),
None => println!("No number"),
}
}

In this example, the first arm will only match if num is Some and the value inside is even.


🦀 Section 4: @ Bindings

The @ operator (pronounced "at") lets you create a variable that holds a value at the same time as you're testing that value to see whether it matches a pattern.

This is useful when you want to test a value against a pattern but also use the value in the arm's code.

fn main() {
let msg = 5;

match msg {
id @ 3..=7 => println!("Found an id in range: {}", id),
_ => println!("Found some other id"),
}
}

Here, the first arm tests if msg is in the range 3..=7. If it is, the value of msg is bound to the variable id, which we can then use in the println!.


✨ Conclusion & Key Takeaways

You've now explored some of the advanced patterns that make match so powerful in Rust. By combining these patterns, you can write code that is both highly expressive and very safe.

Let's summarize the key takeaways:

  • You can match on a range of values with ..=.
  • A match guard (if condition) can be added to a pattern for more complex logic.
  • @ bindings allow you to bind a value to a variable while also testing it against a pattern.

Challenge Yourself: Write a program with a match expression that takes an Option<i32>. Use a match guard to check if the number inside the Some is greater than 10. Print different messages for a number greater than 10, a number less than or equal to 10, and None.


➡️ Next Steps

This concludes our series on control flow. You've learned about if, loop, while, for, and match. You now have a complete toolkit for controlling the execution of your Rust programs.

In our next series, "Working with Collections - Vectors, Strings, Hash Maps", we will take a deep dive into some of the most common and useful data structures in Rust.

You've mastered the fundamentals of control flow. Now, let's learn how to manage collections of data!