Rust Learning Note: Pattern Matching

·

4 min read

This blog is a summay of Chapter 2.6 and 2.7 of Rust Course (course.rs)

match expression

The match expression follows the grammar below:

match target {
    pattern1 => expression1,    // returns expression 1 if target matches pattern1
    pattern2 => {    // can add statements before the returned expression
        statement1, 
        statement2,
        expression2
    },
    ...
    _ => expression3    // _ indicates any other cases
}

The match expression compares the target with patterns listed below (it is similar to switch statements in many languages to some extent). If a pattern "matches with" the target variable, the codes in the pattern are executed, returning a evaluated expression. Since match returns an expression, it can also be used for assignment statements.

The match expression must include all possible cases of target. The "_" in the end represents any other cases not included above, similar to "default" statement in switch.

Here is an example use of match:

enum Coin {
    Penny, 
    Nickel,
    Dime,
    Quarter
}

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

match expression can also retrieve the values inside the patterns. In the example below, we use match expression to retrieve the "Hello Rust", x y, and r g stored in enumerate elements.

enum Action {
    Say(String),
    MoveTo(i32, i32),
    ChangeColorRGB(u16, u16, u16)
}

fn main() {
    let actions = [
        Action::Say("Hello Rust".to_string()),
        Action::MoveTo(1, 2),
        Action::ChangeColorRGB(255, 255, 0)
    ];
    for action in actions {
        match action {
            Action::Say(s) => {    // s will be "Hello Rust"
                println!("{}", s);
            },
            Action::MoveTo(x, y) => {    // x, y will be 1, 2
                println!("{}, {}", x, y);
            },
            Action::ChangeColorRGB(r, g, _) => {
                println!("{}, {}", r, g);    // here b is not used
            }
        }
    }
}

match guard refers to additing an if statements after one pattern branch to provide additional conditions for pattern matching. For example, the code below shows checking "x < 5" with match guard.

let num = Some(4);

match num {
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}

@ operator allows binding a value to another variable. It is used when we want to simultaneouly have restrict on the pattern variable and use the variable. Consider the case below:

enum Message {
    Hello {id: i32}
}

let msg = Message::Hello {id: 5};
match msg {
    Message::Hello {id: id_variable @ 3..=7} => {
        println!("{}", id_variable)
    },    // case 1
    Message::Hello {id: 10..=12} => {
        println!("Found an id in 10-12")
    },   // case 2
    Message::Hello {id} => {
        println!("{}", id)
    },    // case 3
}

In case 1, we restrict the value of id in 3-7, and inside the pattern we still want to use the value of id. We can use "id_variable @" to refer to the "id" in msg. In case 2, we cannot use "id" since id does not refer to a specific value but a range (the variable name "id" in case 2 shadows the "id" in message). In case 3, the variable id is unaffected, but we cannot restrict id in a given range.

if-let expression

As mentioned previously, match expression must cover all possible patterns. However, sometimes we only want to check the matching of one pattern. In this case, we can use if-let expression instead.

if let target = pattern {
    expression
}

This is equivalent to

match target {
    pattern => expression,
    _ => ()
}

matches! macro

matches! can compare one expression and a pattern and return true/false indicating whether the expression matches with the pattern. For example:

let bar = Some(4)
assert!(matches!(bar, Some(x) if x > 2));

Variable Shadowing in Pattern Matching

When we use a variable name inside the pattern matching that is the same as the variable name outside the scope, the variable inside would override (shadow) the name outside.

fn main() {
    let age = Some(30);
    if let Some(age) = age {
        println!("{}", age);
    }
}

In this if-let statement, the printed value will be 30 instead of Some(30). This is because in the statement the variable "age" inside, which has value 30 (Some(age) = Some(30)), shadows the "age" variable Some(30).

The same would happen for match statement, even though it may be less obvious:

fn main() {
    let age = Some(30);
    match age {
        Some(age) => println!("{}", x),
        _ => ()
    }
}

The printed value is also 30 for the same reason

Did you find this article valuable?

Support Raine by becoming a sponsor. Any amount is appreciated!