5/5
## Mastering Rust's `match` Control Flow In Rust, control flow is not just about `if/else` statements and loops. One of the most powerful and frequently used constructs is the `match` keyword. While it may look like a `switch` statement from other languages, `match` is a far more capable expression that enables robust pattern matching, ensuring you handle every possible case, which is a cornerstone of Rust's safety guarantees. ### Basic `match` Syntax At its core, `match` allows you to compare a value against a series of patterns. When it finds a matching pattern, it executes the code associated with that pattern. Each pattern and its associated code is called an "arm." Let's look at a basic example. Here, we want to perform a different action based on the value of an integer `x`. ```rust fn main() { let x = 1; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), } } ``` In this snippet, we are matching on the value of `x`. The `match` expression checks each arm in order. Since `x` is `1`, it matches the first arm, `1 => println!("one")`, and the program prints "one". ### The Rule of Exhaustiveness If you try to compile the code above as-is, you will encounter a compiler error. This is because `match` in Rust must be **exhaustive**. This means you must provide an arm for every possible value that the type can hold. Our variable `x` is an `i32` (a 32-bit integer), but we've only handled the values 1, 2, and 3. The compiler will stop you with a helpful message: ``` error[E0004]: non-exhaustive patterns: `i32::MIN..=0_i32` and `4_i32..=i32::MAX` not covered --> src/main.rs:5:11 | 5 | match x { | ^ patterns `i32::MIN..=0_i32` and `4_i32..=i32::MAX` not covered ``` To satisfy the compiler and make our code robust, we need a way to handle all other possible values. This is done using the special wildcard pattern `_`, which acts as a catch-all. ```rust fn main() { let x = 5; // Changed to demonstrate the catch-all match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), // The wildcard `_` handles all other possible values. _ => println!("others"), } } ``` Now, if `x` is any value other than 1, 2, or 3, the final arm will execute, printing "others". This exhaustiveness check is a key feature that prevents bugs by forcing you to consider all outcomes. ### Advanced Pattern Matching The power of `match` extends beyond simple value checks. You can match against multiple values, ranges, and even bind the matched value to a new variable for use within the arm's expression. #### Matching Multiple Values and Ranges You can make a single arm handle several patterns by using the `|` (OR) operator. To match against a continuous sequence of values, you can use the inclusive range syntax `..=`. ```rust fn main() { let x = 7; match x { // This arm matches if x is 1, 2, OR 3. 1 | 2 | 3 => println!("1, 2, or 3"), // This arm matches any number from 4 to 10, inclusive. 4..=10 => println!("4 to 10"), _ => println!("others"), } } ``` In this example, since `x` is `7`, it falls into the `4..=10` range, and the program will print "4 to 10". #### Binding Matched Values with `@` Sometimes you want to match against a range but also need to use the specific value that was matched. You can bind the value to a variable using the `@` symbol. ```rust fn main() { let x = 10; match x { // 'i' will be bound to the value of x if it's in the range 1..=10. i @ 1..=10 => println!("1 to 10: found {}", i), _ => println!("others"), } } ``` Here, `x` matches the range `1..=10`. The value of `x` (which is 10) is bound to the variable `i`, which we can then use in our `println!` macro. The output will be: `1 to 10: found 10`. ### Common Use Cases: Handling `Option` and `Result` The most idiomatic and powerful use of `match` is for handling enums, especially the standard library's `Option<T>` and `Result<T, E>` types. `match` forces you to handle every variant of the enum, making your code safer. #### Matching `Option<T>` An `Option` can either be `Some(value)`, containing a value, or `None`, indicating the absence of a value. `match` is the perfect tool for safely unwrapping it. ```rust fn process_optional(x: Option<i32>) { match x { // If x is Some, the inner value is bound to `val`. Some(val) => println!("Option contains the value: {val}"), // Handles the case where x is None. None => println!("Option is None"), } } fn main() { process_optional(Some(9)); process_optional(None); } ``` #### Matching `Result<T, E>` Similarly, a `Result` represents either success, `Ok(value)`, or failure, `Err(error)`. Using `match` is the canonical way to handle both outcomes explicitly. ```rust fn process_result(res: Result<i32, String>) { match res { // Handles the success case and binds the value to `val`. Ok(val) => println!("Success! The value is {val}"), // Handles the error case and binds the error to `err`. Err(err) => println!("Error: {err}"), } } fn main() { process_result(Ok(123)); process_result(Err("failed to process data".to_string())); } ``` ### Using `match` as an Expression In Rust, most control flow constructs are expressions, meaning they evaluate to a value. This is true for `match` as well. You can use a `match` block to determine the value of a variable. The value returned is the result of the expression from the arm that executes. This is extremely useful for transforming data or providing default values. ```rust fn main() { let x: Option<i32> = Some(9); // let x: Option<i32> = None; // Try uncommenting this line // The result of the match block is assigned to `z`. let z: i32 = match x { Some(val) => val, // If Some, return the inner value. None => 0, // If None, return a default value of 0. }; println!("The value of z is: {z}"); } ``` If `x` is `Some(9)`, the first arm executes, and the expression `val` (which is 9) is returned and assigned to `z`. If `x` were `None`, the second arm would execute, returning `0`. This pattern provides a clean and safe way to get a value out of an `Option`, with a fallback for the `None` case.
match
Control FlowIn Rust, control flow is not just about if/else
statements and loops. One of the most powerful and frequently used constructs is the match
keyword. While it may look like a switch
statement from other languages, match
is a far more capable expression that enables robust pattern matching, ensuring you handle every possible case, which is a cornerstone of Rust's safety guarantees.
match
SyntaxAt its core, match
allows you to compare a value against a series of patterns. When it finds a matching pattern, it executes the code associated with that pattern. Each pattern and its associated code is called an "arm."
Let's look at a basic example. Here, we want to perform a different action based on the value of an integer x
.
In this snippet, we are matching on the value of x
. The match
expression checks each arm in order. Since x
is 1
, it matches the first arm, 1 => println!("one")
, and the program prints "one".
If you try to compile the code above as-is, you will encounter a compiler error. This is because match
in Rust must be exhaustive. This means you must provide an arm for every possible value that the type can hold. Our variable x
is an i32
(a 32-bit integer), but we've only handled the values 1, 2, and 3.
The compiler will stop you with a helpful message:
To satisfy the compiler and make our code robust, we need a way to handle all other possible values. This is done using the special wildcard pattern _
, which acts as a catch-all.
Now, if x
is any value other than 1, 2, or 3, the final arm will execute, printing "others". This exhaustiveness check is a key feature that prevents bugs by forcing you to consider all outcomes.
The power of match
extends beyond simple value checks. You can match against multiple values, ranges, and even bind the matched value to a new variable for use within the arm's expression.
You can make a single arm handle several patterns by using the |
(OR) operator. To match against a continuous sequence of values, you can use the inclusive range syntax ..=
.
In this example, since x
is 7
, it falls into the 4..=10
range, and the program will print "4 to 10".
@
Sometimes you want to match against a range but also need to use the specific value that was matched. You can bind the value to a variable using the @
symbol.
Here, x
matches the range 1..=10
. The value of x
(which is 10) is bound to the variable i
, which we can then use in our println!
macro. The output will be: 1 to 10: found 10
.
Option
and Result
The most idiomatic and powerful use of match
is for handling enums, especially the standard library's Option<T>
and Result<T, E>
types. match
forces you to handle every variant of the enum, making your code safer.
Option<T>
An Option
can either be Some(value)
, containing a value, or None
, indicating the absence of a value. match
is the perfect tool for safely unwrapping it.
Result<T, E>
Similarly, a Result
represents either success, Ok(value)
, or failure, Err(error)
. Using match
is the canonical way to handle both outcomes explicitly.
match
as an ExpressionIn Rust, most control flow constructs are expressions, meaning they evaluate to a value. This is true for match
as well. You can use a match
block to determine the value of a variable. The value returned is the result of the expression from the arm that executes.
This is extremely useful for transforming data or providing default values.
If x
is Some(9)
, the first arm executes, and the expression val
(which is 9) is returned and assigned to z
. If x
were None
, the second arm would execute, returning 0
. This pattern provides a clean and safe way to get a value out of an Option
, with a fallback for the None
case.
An essential introduction to Mastering Rust's `match` Control Flow - Discover the power of Rust's `match` expression, a tool for pattern matching that guarantees every case is handled. Learn to use `match` with ranges, enums like `Option` and `Result`, and as an expression that returns a value.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025
Rust Developer
Rust Programming BasicsDuration: 6min
Duration: 18min
Duration: 47min
Duration: 15min
Duration: 19min
Duration: 8min
Duration: 12min
Duration: 46min
Duration: 14min
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025