Match

Often, a simple if/else isn't enough, because you have more than two possible options. Also, else conditions can get incredibly complicated, so what's the solution?

Rust has a keyword, match, that allows you to replace complicated if/else groupings with something more powerful. Check it out:

let x = 5;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    4 => println!("four"),
    5 => println!("five"),
    _ => println!("something else"),
}

match takes an expression and then branches based on its value. Each arm of the branch is of the form val => expression. When the value matches, that arm's expression will be evaluated. It's called match because of the term 'pattern matching', which match is an implementation of.

So what's the big advantage here? Well, there are a few. First of all, match enforces exhaustiveness checking. Do you see that last arm, the one with the underscore (_)? If we remove that arm, Rust will give us an error:

error: non-exhaustive patterns: `_` not covered

In other words, Rust is trying to tell us we forgot a value. Because x is an integer, Rust knows that it can have a number of different values – for example, 6. Without the _, however, there is no arm that could match, and so Rust refuses to compile. _ acts like a catch-all arm. If none of the other arms match, the arm with _ will, and since we have this catch-all arm, we now have an arm for every possible value of x, and so our program will compile successfully.

match statements also destructure enums, as well. Remember this code from the section on enums?

use std::cmp::Ordering;

fn cmp(a: i32, b: i32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

fn main() {
    let x = 5;
    let y = 10;

    let ordering = cmp(x, y);

    if ordering == Ordering::Less {
        println!("less");
    } else if ordering == Ordering::Greater {
        println!("greater");
    } else if ordering == Ordering::Equal {
        println!("equal");
    }
}

We can re-write this as a match:

use std::cmp::Ordering;

fn cmp(a: i32, b: i32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

fn main() {
    let x = 5;
    let y = 10;

    match cmp(x, y) {
        Ordering::Less => println!("less"),
        Ordering::Greater => println!("greater"),
        Ordering::Equal => println!("equal"),
    }
}

This version has way less noise, and it also checks exhaustively to make sure that we have covered all possible variants of Ordering. With our if/else version, if we had forgotten the Greater case, for example, our program would have happily compiled. If we forget in the match, it will not. Rust helps us make sure to cover all of our bases.

match expressions also allow us to get the values contained in an enum (also known as destructuring) as follows:

enum OptionalInt {
    Value(i32),
    Missing,
}

fn main() {
    let x = OptionalInt::Value(5);
    let y = OptionalInt::Missing;

    match x {
        OptionalInt::Value(n) => println!("x is {}", n),
        OptionalInt::Missing => println!("x is missing!"),
    }

    match y {
        OptionalInt::Value(n) => println!("y is {}", n),
        OptionalInt::Missing => println!("y is missing!"),
    }
}

That is how you can get and use the values contained in enums. It can also allow us to handle errors or unexpected computations; for example, a function that is not guaranteed to be able to compute a result (an i32 here) could return an OptionalInt, and we would handle that value with a match. As you can see, enum and match used together are quite useful!

match is also an expression, which means we can use it on the right-hand side of a let binding or directly where an expression is used. We could also implement the previous example like this:

use std::cmp::Ordering;

fn cmp(a: i32, b: i32) -> Ordering {
    if a < b { Ordering::Less }
    else if a > b { Ordering::Greater }
    else { Ordering::Equal }
}

fn main() {
    let x = 5;
    let y = 10;

    println!("{}", match cmp(x, y) {
        Ordering::Less => "less",
        Ordering::Greater => "greater",
        Ordering::Equal => "equal",
    });
}

Sometimes, it's a nice pattern.