3 min read

Rust Book - Chapter 6.2

match control flow construct

Rust's match lets you compare a value against a series of patterns and then execute code based on which pattern matches. The power of match comes from the expressiveness of the patterns and the fact that the complier confirms that all possible cases are handled

  • the match expression is similar to the if expression. The big difference is that match evalues to any type, whereas if expression evaluates to a boolean
  • A simple analogy for match expression would be to think of a coin-sorting machine where coins of different sizes slide down a track with variously sized holes, with each coin falling through the first hole that it fits into. In the same way, values go through each pattern in a match, and at the first pattern that fits, the value falls into the associated code block to be used during execution.

A match consist of the keyword, followed by an expression, next: the match arms. An arm has: a pattern and some code. Each arm is separated from the next by a comma.

A match expression compares the value against the pattern of each arm, in order.

You can omit curly brackets from an arm if the arm code is short. Otherwise for multi-line arm blocks, you must use curly brackets, and the comma separation for the arm in then optional.

enum Coin {                         
    TwoEuro,                        
    OneEuro,                        
    FiftyCent,                      
    TwentyCent,                     
    TenCent,                        
    FiveCent,                       
    TwoCent,                        
    OneCent,                        
}                                   
                                    
fn value_in_cent(coin: Coin) -> u8 {
    match coin {                    
        Coin::TwoEuro => 200,       
        Coin::OneEuro => 100,       
        Coin::FiftyCent => 50,      
        Coin::TwentyCent => 20,     
        Coin::TenCent => 10,        
        Coin::FiveCent => 5,        
        Coin::TwoCent => 2,         
        Coin::OneCent => 1,         
    }                               
}                                   
                                    

1. Patterns that bind to values

A very powerful feature of the match arm is that it can bind to the parts of the value that matches the pattern, allowing to extract values out of the enum variants. how cool is this. 😎. This is currently one of my favourite features of the language. In the following example, I demonstrate how to encode information about account types in an enum and use the same to determine whether to give an account sign up bonus; using match construct.

//Program to practice match construct for pattern matching with enums

#[derive(Debug)]
enum Account {
    Student(TertiaryInstitution),
    Graduate(Discipline),
    Employed,
    Unemployed,
}

#[derive(Debug)]
enum TertiaryInstitution {
    University,
    Polytechnic,
    CollegeOfEducation,
}

#[derive(Debug)]
enum Discipline {
    HumanitiesAndSocialSciences,
    NaturalSciencesAndMathematics,
    Education,
}

fn should_get_signup_bonus(account: &Account) -> bool {
    match account {
        Account::Unemployed => true,
        Account::Employed => false,
        Account::Student(institution) => match institution {
            TertiaryInstitution::University => true,
            TertiaryInstitution::Polytechnic => true,
            TertiaryInstitution::CollegeOfEducation => false,
        },
        Account::Graduate(discipline) => match discipline {
            Discipline::HumanitiesAndSocialSciences => false,
            Discipline::NaturalSciencesAndMathematics => true,
            Discipline::Education => true,
        },
    }
}

fn main() {
    //create an array of the account type
    let account_types: [Account; 8] = [
        Account::Graduate(Discipline::HumanitiesAndSocialSciences),
        Account::Graduate(Discipline::NaturalSciencesAndMathematics),
        Account::Graduate(Discipline::Education),
        Account::Student(TertiaryInstitution::University),
        Account::Student(TertiaryInstitution::Polytechnic),
        Account::Student(TertiaryInstitution::CollegeOfEducation),
        Account::Employed,
        Account::Unemployed,
    ];

    //iterate over the account types and print whether they are eligible for the sign up bonus or not
    for element in account_types {
        println!(
            "Is Account {:?} eligible for sign up bonus? {:?}",
            element,
            match should_get_signup_bonus(&element) {
                true => "Yes",
                false => "No",
            }
        );
    }
}

cargo run
Compiling enum_example v0.1.0 (/Users/nathanieledeki/projects/rust/the-book/chapter_6/enum_example)
Finished dev [unoptimized + debuginfo] target(s) in 0.14s
Running target/debug/enum_example
Is Account Graduate(HumanitiesAndSocialSciences) eligible for sign up bonus? "No"
Is Account Graduate(NaturalSciencesAndMathematics) eligible for sign up bonus? "Yes"
Is Account Graduate(Education) eligible for sign up bonus? "Yes"
Is Account Student(University) eligible for sign up bonus? "Yes"
Is Account Student(Polytechnic) eligible for sign up bonus? "Yes"
Is Account Student(CollegeOfEducation) eligible for sign up bonus? "No"
Is Account Employed eligible for sign up bonus? "No"
Is Account Unemployed eligible for sign up bonus? "Yes"

2. Match with Option<T>

fn plus_one(option: Option<i32>) -> Option<i32> {
    match option {
        None => None,
        Some(value) => Some(value + 1),
    }
}

fn main() {
    let six_option = plus_one(Some(5));
    print!("six plus one {:?}", six_option);
}

3. Matches are exhaustive

This is one of the best things about the match construct. The compiler will check that the arms' patterns cover all possibilities. Otherwise, the code won't compile.

If we omitted the arm for None in the previous example, we would get an error that looks like this:

cargo run
Compiling match_with_option v0.1.0 (/Users/nathanieledeki/projects/rust/the-book/chapter_6/match_with_option)
error[E0004]: non-exhaustive patterns: None not covered
--> src/main.rs:2:11
|
2 | match option {
| ^^^^^^ pattern None not covered
|
note: Option<i32> defined here
--> /Users/nathanieledeki/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:570:1
|
570 | pub enum Option {
| ^^^^^^^^^^^^^^^^^^
...
574 | None,
| ---- not covered
= note: the matched value is of type Option<i32>
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
3 ~ Some(value) => Some(value + 1),
4 ~ None => todo!(),
|

For more information about this error, try rustc --explain E0004.
error: could not compile match_with_option (bin "match_with_option") due to 1 previous error

4. Catch-all Pattern and the _ Placeholder

The pattern _ in the match arm tells rust that we don't care about the value of the pattern, so rust doesn't bind it. However, the catch-all must be last in the match arms, otherwise the code won't compile.

let dice_roll = 9;
match dice_roll {
    3 => add_fancy_hat(),
    7 => remove_fancy_hat(),
    _ => reroll(),
}