18. Patterns and Matching

Introduction

Learning objectives

  • Identify the available patterns we can match against
  • Understand the difference between refutable and irrefutable patterns

Patterns and Matching

Patterns are a special syntax in Rust for matching against the structure of types …

We can control the flow of a program by matching against patterns.

Patterns

Patterns describe the shape of data

Ex: \(m \times n\) matrix vs. \(n \times p\) matrix

Types of patterns

  • Literals (1)
  • Destructured arrays, enums, structs, tuples ((x, y) = (1, 2))
  • Variables (match x ...)
  • Wildcards (_)
  • Placeholders (_)

Where patterns can be used

match expression

match x {
    None => None,
    Some(i) => Some(i + 1),
}
match VALUE {
  PATTERN => EXPRESSION,
  PATTERN => EXPRESSION,
  ...
}

if let expression

if let PATTERN = VALUE {
  EXPRESSION
}
if let PATTERN = VALUE {
  EXPRESSION
} else if PATTERN = VALUE {
  EXPRESSION
}
...

if let expression

fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

    if let Some(color) = favorite_color {
        println!("Using your favorite color, {color}, as the background");
    } else if is_tuesday { // unrelated to favorite_color
        println!("Tuesday is green day!");
    } else if let Ok(age) = age {
        if age > 30 { // shadow variable age
            println!("Using purple as the background color");
        } else {
            println!("Using orange as the background color");
        }
    } else {
        println!("Using blue as the background color");
    }
}

while let expression

while let PATTERN = VALUE {
  EXPRESSION
}
    let mut stack = Vec::new();

    stack.push(1);
    stack.push(2);
    stack.push(3);

    while let Some(top) = stack.pop() {
        println!("{top}");
    }

for loop

for PATTERN in VALUE {
  EXPRESSION
}
    let v = vec!['a', 'b', 'c'];

    for (index, value) in v.iter().enumerate() {
        println!("{value} is at index {index}");
    }

let

let PATTERN = EXPRESSION
let x = 5;

let (x, y, z) = (1, 2, 3);

fun parameters

fun name(PATTERN: type) {}

fn foo(x: i32) {}

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({x}, {y})");
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

Refutable vs. irrefutable patterns

Refutable vs. irrefutable

Patterns that can fail to match for some possible value are refutable.

Patterns that will match for any possible value passed are irrefutable.

Refutable vs. irrefutable

// refutable
if let Some(x) = value {};

// irrefutable
let x = 5;

Refutable vs. irrefutable

Some places require refutable patterns, some places require irrefutable patterns

Refutable pattern - irrefutable expected

fn main() {
    let some_option_value: Option<i32> = None;
    let Some(x) = some_option_value;
}
fn main() {
    let some_option_value: Option<i32> = None;
    if let Some(x) = some_option_value {
        println!("{x}");
    }
}

Irrefutable pattern - refutable expected

fn main() {
    if let x = 5 {
        println!("{x}");
    };
}

Pattern Syntax

Literals

Usage: respond to specific values

    let x = 1;

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

Named variables

Usage: match any value

    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:?}"),
    }

    println!("at the end: x = {x:?}, y = {y}");

Multiple patterns

Usage: match against multiple patterns

    let x = 1;

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

Multiple patterns in a range

Usage: match against a range of values

    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 - structs

Usage: match against different parts

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);
}
struct Point {
    x: i32,
    y: i32,
}

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

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

Destructuring - enums

Usage: match against different parts

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

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

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.");
        }
        Message::Move { x, y } => {
            println!("Move in the x direction {x} and in the y direction {y}");
        }
        Message::Write(text) => {
            println!("Text message: {text}");
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change the color to red {r}, green {g}, and blue {b}")
        }
    }
}

Destructuring - nested enums

Usage: match against different parts

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

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color),
}

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

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

Destructuring - structs & tuples

Usage: match against different parts

let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });

Ignoring values in a pattern

Ignore a value

Usage: ignore an entire value

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

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

Ignore part of a value

Usage: ignore only part of a value

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

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

Ignore a nested value

Usage: ignore a nested value

    let mut setting_value = Some(5);
    let new_setting_value = Some(10);

    match (setting_value, new_setting_value) {
        (Some(_), Some(_)) => {
            println!("Can't overwrite an existing customized value");
        }
        _ => {
            setting_value = new_setting_value;
        }
    }

    println!("setting is {setting_value:?}");

Ignore multiple places

Usage: ignore multiple places in a single pattern

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

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

Ignore unused variable

Usage: ignore an unused variable

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

Ignore remaining parts

Usage: use first part(s) of a value, ignore the rest

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

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

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

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

Match guards

A match guard is an additional if condition, specified after the pattern in a match arm, that must also match for that arm to be chosen.

Match guards

let num = Some(4);

match num {
    Some(x) if x % 2 == 0 => println!("The number {x} is even"),
    Some(x) => println!("The number {x} is odd"),
    None => (),
}

Match guards

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

    match x {
        Some(50) => println!("Got 50"),
        Some(n) if n == y => println!("Matched, n = {n}"),
        _ => println!("Default case, x = {x:?}"),
    }

    println!("at the end: x = {x:?}, y = {y}");
}

Match guard

let x = 4;
let y = false
match x {
    4 | 5 | 6 if y => println!("yes"),
    _ => println!("no"),
}

@ Bindings

The at operator @ lets us create a variable that holds a value at the same time as we’re testing that value for a pattern match

@ Bindings

enum Message {
    Hello { id: i32 },

let msg = Message::Hello { id: 5 }
match msg {
    Message::Hello {
        id: id_variable @ 3..=7,
    } => println!("Found an id in range: {id_variable}"),
    Message::Hello { id: 10..=12 } => {
        println!("Found an id in another range")
    }
    Message::Hello { id } => println!("Found some other id: {id}"),
}