Pattern Syntax in Rust

All valid syntax to use with pattern matching.

Matching Literals

This pattern is useful when we want our code to take action when it receives a concrete value.

fn main() {
    let x = 1;

    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}

Matching Named Variables

Second branch is using named variable pattern to match other variable shadowing any variable outside of match block.

fn main() {
    let x = Some(5);
    let y = 10;
    
    match x {
        Some(50) => println!("Got 50"),
        Some(y) => println!("Matched, y = {:?}", y),
        _ => println!("Default case, x = {:?}", x).
    }
}

Multiple patterns

fn main() {
    let x = 1;

    match x {
        1 | 2 => println!("one or two"), // using OR operator |
        2 => println!("three"),
        3 | 4 | 5 => println!("greater than 2 or equal to 5"),
        _ => println!("anything"),
    }
}

Matching Ranges of Values

The range operator only works on numeric values and characters.

fn main() {
    let x = 5;

    match x {
        1..=5 => println!("one through five"),
        _ => println!("something else"),
    }

    let x = 'c';

    match x {
        'a'..='j' => println!("early ASCII letter"),
        'k'..='z' => println!("late ASCII letter"),
        _ => println!("something else"),
    }
}

Destructuring to Break Apart Values

Destructuring structs

We deconstruct p a Point instance to create a and b.

a is going to be mapped to whatever values contained in x and b to y.

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7};

    let point { x: a, y: b} = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
}

However it’s common to have variables inside of a pattern match the field names, so:

let point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);

When destructuring a struct we can use named variables and literals.

fn main() {
    let p = Point { x: 0, y: 7};

    match p {
        Point { x, y: 0 } => {    // `y` must be zero
            println!("On the x axis at {}", x)
        },
        Point { x: 0, y} => {     // `x` must be zero
            println!("On the y axis at {}", y)
        },
        Point { x, y } => {
            println!("On neither axis: ({}, {})", x, y)
        }
    }
}

Destructuring Enums

enum Message {
    Quit,                          // unit like variant 
    Move { x: i32, y: i32},        // struct variant
    Write(String),                 // tuple variant called `Write`
    ChangeColor(i32, i32, i32),    // tuple variant with 3 values called `ChangeColor`
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            // a semicolon isn't required if there is only one expression
            println!("Quit");
        }
        Message::Move { x, y } => {
            println!("Move to x: {} y: {}", x, y)
        }
        Message::Write(text) => {
            println!("Text message: {}", text)
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change color: red {}, green {}, and blue {}", r, g, b)
        }
    }
}

Destructuring nested structs and Enums

enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Message {
    Quit,                          // unit like variant 
    Move { x: i32, y: i32},        // struct variant
    Write(String),                 // tuple variant called `Write`
    ChangeColor(Color),            // tuple variant with 3 values called `ChangeColor`
}

fn main() {
    let msg = Message::ChangeColor(
        Color::Hsv(0, 160, 255)
    );

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!("Change color: red {}, green {}, and blue {}", r, g, b)
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!("Change color: hue {}, saturation {}, and value {}", h, s, v)
        }
    }
    _ => (),
}

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    // destruct 3 and 10 into `feet` and `inches` 
    let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
}

Ignoring Values in a Pattern

_ can be used anywhere to signify ingoring of the value.

This could be useful when we need a function signature to be something specific but we’re not gonna use all the arguments passed in. The _ avoid compiler warning for unused parameters.

fn main() {
    foo(3, y);
}

fn foo(_, i32, y: i32) {
    println!("This code only uses the y parameter: {}", y);
}

To ignore part of a value

If the setting has no value then we can set a value, however if the setting already has a value then we can’t modify it.

fn main() {
    let mut setting_value = Some(5);
    let new_setting_value = Some(10);

    match (setting_value, new_setting_value) {
        // if both are `Some<T>` variant then 
        (Some(_), Some(_)) => {
            println!("Can't overwrite an existing customized value")
        }
        _ => {
            setting_value = new_setting_value
        }
    }
    
    println!("setting is {:?}", setting_value);
}

To ignore values at multiple places withing one pattern

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, _, third, _, fifth) => {
            println!("Some numbers: {}, {}, {}", first, third, fifth)
        }
    }
}

Unused variables

The compiler won’t complain.

fn main() {
    let _x = 5;
    let y = 10;
}

Prefixing a name with _ is different than just using _.

Prefixing a variable name with _ still binds the value.

_s is still binding with s.

fn main() {
    let s = Some(String::from("Hello!"));

    // to avoid compiler complain from not using `s` inside we prefix it with `_`
    // but this moves value to `_s`
    if let Some(_s) = s {
        println!("found a string")
    }

    println!("{:?}", s);
    //               ^ error: moved value
}

Instead if we would have done this, then it would successfully compile:

fn main() {
    let s = Some(String::from("Hello!"));

    if let Some(_) = s {
        println!("found a string")
    }

    println!("{:?}", s);
}

Range syntax to ignore values

fn main() {
    struct Point {
        x: i32,
        y: i32,
    }

    let origin = Point { x: 0, y:0, z: 0 };

    match origin {
        // ignore all field except `x`
        Point { x, .. } => println!("x is {}", x),
    }
}
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, ..., last) => {
            println!("Some numbers: {}, {}", first, last);
        }
    }
}

But they should be unambigous. Something like this won’t work:

(..., second, ...) => { }

Match Guards

A match guard is an additional if condition specified after the pattern in a match arm that must also match along with the pattern.

Useful for expressing complex ideas that patterns themselves cannot express.

fn main() {
    let num = Some(4);

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

Match guards also solve issues with shadowing inside the match block.

Here in this example, we want to execute some code when x is equal to y.

We can’t use y inside the pattern because it will shadow the outside y. But we could use y inside of our match guard.

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

    match num {
        // match on any `Some<T>`; bind it to `n` and compare it to `y`.
        Some(n) if n == y => println!("Matched: x = {}", n),
        _ => println!("Default case, x = {:?}", x),
    }
}

Multiple patterns with match guards:

fn main() {
    let x = 4;
    let y = false;

    match x {
        // `x` has to either 4 or 5 or 6 AND `y` has to be true
        4 | 5 | 6 if y => println!("yes"),
        _ => println!("no"),
    }
}

Bindings

@ operator let’s us create a variable that holds a value at the same time we’re testing that value to see whether it matches a pattern.

fn main() {
    enum Message {
        Hello { id: i32 },
    }

    let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello {
            // match `id` between 3 and 7 and also store it in `id_variable`
            // can be same as field name; instead of `id_variable`
            id: id_variable @ 3..=7, 
        } => println!("Found and id in range: {}", id_variable),
        Message::Hello { id: 10..=12 } => { // otherwise simpy use range operator
            println!("Found an id in another range")
        }
        Message::Hello { id } => {          // to bind the variable
            println!("Found some other id: {}", id)
        }
    }
}