[Rust Guide] 6.3. The Match Control Flow Operator

rust dev.to

6.3.1. What Is match?

match allows a value to be compared against a series of patterns and executes the code corresponding to the matching pattern. Patterns can be literals, variable names, wildcards, and more.

Think of a match expression as a coin-sorting machine: coins slide down a track with holes of different sizes, and each coin falls through the first hole that fits it. In the same way, a value goes through each pattern in match, and when it “fits” the first pattern, it falls into the associated code block that will be used during execution.

6.3.2. Practical Use of match

Let’s look at an example: write a function that takes an unknown U.S. coin and determines which coin it is in a counting-machine-like way, then returns its value in cents.

enum Coin {
    Penny,// 1 cent
    Nickel,// 5 cents
    Dime,// 10 cents
    Quarter,// 25 cents
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
Enter fullscreen mode Exit fullscreen mode
  • The match keyword is followed by an expression, which in this example is the value coin. This looks very similar to the conditional expression used in if, but there is one big difference: the condition of if must be a boolean value, while match can work with any type. In this example, the type of coin is the Coin enum we defined in the first line.

  • Next comes the braces. Inside the braces there are four branches (also called arms), and each branch is made up of a pattern to match and the code corresponding to that pattern. The first branch, Coin::Penny => 1,, uses Coin::Penny as its pattern. The => separates the pattern from the code to run, and here the code to run is the value 1, meaning it returns 1. Different branches are separated by commas.

  • When a match expression runs, it compares the expression after match—here, coin—with the branches inside from top to bottom. If a pattern matches the value, the code associated with that pattern runs. If it does not match, the next branch is checked. The code expression corresponding to the successful branch is returned as the value of the entire match expression.
    For example, if match matches a 5-cent coin, that is, Coin::Nickel, then the whole expression evaluates to 5. And because the match expression is the last expression in value_in_cents, its value—5—is returned by the function.

  • Here each branch’s code is very simple, so => is enough. But if one branch contains multiple lines of code, you need to wrap those lines in braces. For example:

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
Enter fullscreen mode Exit fullscreen mode

6.3.3. Patterns That Bind Values

Branches in a match can bind to part of the matched value, allowing you to extract values from enum variants.

For example, a friend is trying to collect all 50 state quarters. When we sort change by coin type, we also label the state name associated with each quarter (there are too many U.S. states, so only Alabama and Alaska are shown here):

#[derive(Debug)] // For easier debug printing
enum UsState {  
    Alabama,  
    Alaska,  
}  

enum Coin {  
    Penny,  
    Nickel,  
    Dime,  
    Quarter(UsState),  
}  

fn value_in_cents(coin: Coin) -> u8 {  
    match coin {  
        Coin::Penny => {  
            println!("Lucky penny!");  
            1  
        },  
        Coin::Nickel => 5,  
        Coin::Dime => 10,  
        Coin::Quarter(state) => {  
            println!("State quarter from {:?}!", state);  
            25  
        }  
    }  
}  

fn main() {  
    let c = Coin::Quarter(UsState::Alaska);  
    println!("{}", value_in_cents(c));  
}
Enter fullscreen mode Exit fullscreen mode
  • Give the Coin variant for a quarter coin a piece of associated data, namely the UsState enum above.

  • In the value_in_cents function, the Quarter branch also needs to be adjusted. The match pattern changes from Coin::Quarter to Coin::Quarter(state), which means the value associated with Coin::Quarter is bound to the variable state, so it can be used in the following block to access that associated value.
    In some situations, the value associated with Coin::Quarter may not be needed. In that case, you can use the wildcard _ to indicate that you do not care about the contents: Coin::Quarter(_)

  • In main, a variable c is declared first, holding Coin::Quarter(UsState::Alaska). In other words, it stores the Coin::Quarter variant and its associated value is the UsState::Alaska variant. Then value_in_cents is called.

Let’s look at the output:

State quarter from Alaska!
25
Enter fullscreen mode Exit fullscreen mode

6.3.4. Matching Option<T>

Let’s analyze the last code example from the previous article:

fn main() {  
    let x: i8 = 5;  
    let y: Option<i8> = Some(5);  

    let sum = match y {  
        Some(value) => x + value, // If y is Some, unwrap it and add  
        None => x,               // If y is None, return x    
    };  
}
Enter fullscreen mode Exit fullscreen mode
  • If y is not None, unwrap it, bind the value associated with Some to value, and return x + value.
  • If y is None, return only the value of x.

6.3.5. match Must Be Exhaustive

Rust requires match to cover all possibilities so that code remains safe and valid.

Make a small modification to the previous code:

fn main() {  
    let x: i8 = 5;  
    let y: Option<i8> = Some(5);  

    let sum = match y {  
        Some(value) => x + value,   
    };  
}
Enter fullscreen mode Exit fullscreen mode

Output:

error[E0004]: non-exhaustive patterns: `None` not covered
   --> src/main.rs:5:21
    |
5   |     let sum = match y {
    |                     ^ pattern `None` not covered
    |
note: `Option<i8>` defined here
   --> /Users/stanyin/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:571:1
    |
571 | pub enum Option<T> {
    | ^^^^^^^^^^^^^^^^^^
...
575 |     None,
    |     ---- not covered
    = note: the matched value is of type `Option<i8>`
help: ensure that all possible cases are handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
    |
6   ~         Some(value) => x + value,
7   ~         None => todo!(),
    |
Enter fullscreen mode Exit fullscreen mode

Rust detected that the possibility of None was not covered, so it reported an error. Once you add a branch to handle None, everything is fine.

If there are too many possibilities or you do not want to handle some of them, you can use the wildcard _.

6.3.6. Wildcards

First write the branches you want to handle as usual, and use the wildcard _ for everything else.

For example: v is a u8 variable, and we want to determine whether v is 0.

use rand::Rng;  // Use an external crate
fn main(){  
    let v: u8 = rand::thread_rng().gen_range(0..=255);  // Generate a random number
    println!("{}", v);  
    match v {  
        0 => println!("zero"),  
        _ => println!("not zero"),  
    }  
}
Enter fullscreen mode Exit fullscreen mode

u8 has 256 possible values, so it is naturally impossible to write one branch for each value using match. Therefore, you can write a branch for 0 and use the wildcard _ for everything else.

Output:

136
not zero
Enter fullscreen mode Exit fullscreen mode
Read Full Tutorial open_in_new
arrow_back Back to Tutorials