BBuzzard

match compares a value against patterns, top to bottom, and runs the first arm that fits. It must be exhaustive: every possible value has an arm, or you add _. match is an expression, so every arm produces the same type.

A guard (if after a pattern) adds a condition. @ binds a name to a value while still testing it against a subpattern. if let is a one-arm match, while let is its loop form, and let else binds on success or diverges on failure. matches! turns a pattern into a bool.

Matching literals, and match as an expression

fn demo_match_basics() {
    println!("-- match basics --");

    for n in [1, 2, 3, 9] {
        let word = match n {
            1 => "one",
            2 => "two",
            3 => "three",
            _ => "other",
        };

        print!("{word} ");
    }

    println!();

    // let n = 5u8;
    // match n { 0 => {}, 1..=100 => {} }
    // error[E0004]: non-exhaustive patterns: `101_u8..=u8::MAX` not covered

    let pair = (0, -2);
    match pair {
        (0, y) => println!("on the y axis at {y}"),
        (x, 0) => println!("on the x axis at {x}"),
        _ => println!("somewhere else"),
    }
}

Guards

fn demo_guards() {
    println!("-- guards --");

    for n in [-3, 8, 7] {
        match n {
            x if x < 0 => println!("{x} neg"),
            x if x % 2 == 0 => println!("{x} even"),
            x => println!("{x} odd"),
        }
    }

    let point = (3, 3);
    match point {
        (x, y) if x == y => println!("on the diagonal"),
        (x, y) if x + y == 0 => println!("on the anti-diagonal"),
        _ => println!("off both"),
    }
}

@ bindings

fn demo_bindings() {
    println!("-- @ bindings --");

    for n in [3, 15, 42] {
        match n {
            k @ 1..=5 => println!("small {k}"),
            k @ (10 | 15 | 20) => println!("round-ish {k}"),
            k => println!("other {k}"),
        }
    }

    let msg = Some(7);
    if let Some(n @ 5..) = msg {
        println!("at least five: {n}");
    }
}

Destructuring enums and structs in arms

#[derive(Debug)]
enum Shape {
    Circle { r: f64 },
    Rect { w: f64, h: f64 },
    Dot,
}

fn demo_destructure() {
    println!("-- destructure --");

    let shapes = [Shape::Circle { r: 1.0 }, Shape::Rect { w: 2.0, h: 3.0 }, Shape::Dot];
    for s in &shapes {
        let area = match s {
            Shape::Circle { r } => std::f64::consts::PI * r * r,
            Shape::Rect { w, h } if w == h => w * w,
            Shape::Rect { w, h } => w * h,
            Shape::Dot => 0.0,
        };

        println!("{s:?} area {area:.2}");
    }
}

if let and if let ... else

fn demo_if_let() {
    println!("-- if let --");

    let maybe = Some("hi");
    if let Some(msg) = maybe {
        println!("{msg}");
    } else {
        println!("nothing");
    }

    let parsed: Result<i32, _> = "12".parse::<i32>();
    if let Ok(n) = parsed
        && n > 10
    {
        println!("parsed a big number {n}");
    }

    let mut stack = vec![1, 2];
    while let Some(top) = stack.pop() {
        print!("{top} ");
    }

    println!();
}

let else keeps the happy path unindented

fn greet(name: Option<&str>) {
    let Some(name) = name else {
        println!("no name");
        return;
    };

    println!("hello {name}");
}

fn parse_pair(s: &str) -> Option<(i32, i32)> {
    let Some((a, b)) = s.split_once(',') else {
        return None;
    };

    let (Ok(a), Ok(b)) = (a.trim().parse(), b.trim().parse()) else {
        return None;
    };

    Some((a, b))
}

fn demo_let_else() {
    println!("-- let else --");

    greet(Some("Ada"));
    greet(None);
    println!("{:?} {:?} {:?}", parse_pair("3, 4"), parse_pair("3"), parse_pair("a,b"));

    // let Some(x) = Some(1);
    // error[E0005]: refutable pattern in local binding (add an else block)
}

matches! for a quick bool

fn demo_matches_macro() {
    println!("-- matches! --");

    let n = Some(3);
    println!("{}", matches!(n, Some(x) if x > 0));

    let v = vec![Ok(1), Err("e"), Ok(2)];
    let oks: Vec<_> = v.into_iter().filter(|r| matches!(r, Ok(_))).collect();
    println!("{oks:?}");

    let c = 'q';
    println!("is vowel? {}", matches!(c, 'a' | 'e' | 'i' | 'o' | 'u'));

    let shape = Shape::Rect { w: 1.0, h: 1.0 };
    println!("is rect? {}", matches!(shape, Shape::Rect { .. }));
}
$ cargo run --bin 08_match
-- match basics --
one two three other 
on the y axis at -2
-- guards --
-3 neg
8 even
7 odd
on the diagonal
-- @ bindings --
small 3
round-ish 15
other 42
at least five: 7
-- destructure --
Circle { r: 1.0 } area 3.14
Rect { w: 2.0, h: 3.0 } area 6.00
Dot area 0.00
-- if let --
hi
parsed a big number 12
2 1 
-- let else --
hello Ada
no name
Some((3, 4)) None None
-- matches! --
true
[Ok(1), Ok(2)]
is vowel? false
is rect? true

Full program

08_match/main.rs · 190 lines
View on GitHub
// 08. match, if let, let else, and matches!
//
// match compares a value against patterns, top to bottom, and runs the first arm that
// fits. It must be exhaustive: every possible value has an arm, or you add _. match is
// an expression, so every arm produces the same type.
//
// A guard (if after a pattern) adds a condition. @ binds a name to a value while still
// testing it against a subpattern. if let is a one-arm match, while let is its loop
// form, and let else binds on success or diverges on failure. matches! turns a pattern
// into a bool.
//
// Run: cargo run --bin 08_match

// Example 1: matching literals, and match as an expression.
fn demo_match_basics() {
    println!("-- match basics --");

    for n in [1, 2, 3, 9] {
        let word = match n {
            1 => "one",
            2 => "two",
            3 => "three",
            _ => "other",
        };

        print!("{word} ");
    }

    println!();

    // let n = 5u8;
    // match n { 0 => {}, 1..=100 => {} }
    // error[E0004]: non-exhaustive patterns: `101_u8..=u8::MAX` not covered

    let pair = (0, -2);
    match pair {
        (0, y) => println!("on the y axis at {y}"),
        (x, 0) => println!("on the x axis at {x}"),
        _ => println!("somewhere else"),
    }
}

// Example 2: guards.
fn demo_guards() {
    println!("-- guards --");

    for n in [-3, 8, 7] {
        match n {
            x if x < 0 => println!("{x} neg"),
            x if x % 2 == 0 => println!("{x} even"),
            x => println!("{x} odd"),
        }
    }

    let point = (3, 3);
    match point {
        (x, y) if x == y => println!("on the diagonal"),
        (x, y) if x + y == 0 => println!("on the anti-diagonal"),
        _ => println!("off both"),
    }
}

// Example 3: @ bindings.
fn demo_bindings() {
    println!("-- @ bindings --");

    for n in [3, 15, 42] {
        match n {
            k @ 1..=5 => println!("small {k}"),
            k @ (10 | 15 | 20) => println!("round-ish {k}"),
            k => println!("other {k}"),
        }
    }

    let msg = Some(7);
    if let Some(n @ 5..) = msg {
        println!("at least five: {n}");
    }
}

#[derive(Debug)]
enum Shape {
    Circle { r: f64 },
    Rect { w: f64, h: f64 },
    Dot,
}

// Example 4: destructuring enums and structs in arms.
fn demo_destructure() {
    println!("-- destructure --");

    let shapes = [Shape::Circle { r: 1.0 }, Shape::Rect { w: 2.0, h: 3.0 }, Shape::Dot];
    for s in &shapes {
        let area = match s {
            Shape::Circle { r } => std::f64::consts::PI * r * r,
            Shape::Rect { w, h } if w == h => w * w,
            Shape::Rect { w, h } => w * h,
            Shape::Dot => 0.0,
        };

        println!("{s:?} area {area:.2}");
    }
}

// Example 5: if let and if let ... else.
fn demo_if_let() {
    println!("-- if let --");

    let maybe = Some("hi");
    if let Some(msg) = maybe {
        println!("{msg}");
    } else {
        println!("nothing");
    }

    let parsed: Result<i32, _> = "12".parse::<i32>();
    if let Ok(n) = parsed
        && n > 10
    {
        println!("parsed a big number {n}");
    }

    let mut stack = vec![1, 2];
    while let Some(top) = stack.pop() {
        print!("{top} ");
    }

    println!();
}

fn greet(name: Option<&str>) {
    let Some(name) = name else {
        println!("no name");
        return;
    };

    println!("hello {name}");
}

fn parse_pair(s: &str) -> Option<(i32, i32)> {
    let Some((a, b)) = s.split_once(',') else {
        return None;
    };

    let (Ok(a), Ok(b)) = (a.trim().parse(), b.trim().parse()) else {
        return None;
    };

    Some((a, b))
}

// Example 6: let else keeps the happy path unindented.
fn demo_let_else() {
    println!("-- let else --");

    greet(Some("Ada"));
    greet(None);
    println!("{:?} {:?} {:?}", parse_pair("3, 4"), parse_pair("3"), parse_pair("a,b"));

    // let Some(x) = Some(1);
    // error[E0005]: refutable pattern in local binding (add an else block)
}

// Example 7: matches! for a quick bool.
fn demo_matches_macro() {
    println!("-- matches! --");

    let n = Some(3);
    println!("{}", matches!(n, Some(x) if x > 0));

    let v = vec![Ok(1), Err("e"), Ok(2)];
    let oks: Vec<_> = v.into_iter().filter(|r| matches!(r, Ok(_))).collect();
    println!("{oks:?}");

    let c = 'q';
    println!("is vowel? {}", matches!(c, 'a' | 'e' | 'i' | 'o' | 'u'));

    let shape = Shape::Rect { w: 1.0, h: 1.0 };
    println!("is rect? {}", matches!(shape, Shape::Rect { .. }));
}

fn main() {
    demo_match_basics();
    demo_guards();
    demo_bindings();
    demo_destructure();
    demo_if_let();
    demo_let_else();
    demo_matches_macro();
}