RustArena

Step 6 of 6

Refutable and irrefutable patterns

Some patterns can fail to match: Some(x) fails on None, 0..=9 fails on 42, [first, ..] fails on an empty slice. They're called refutable. Others match every possible value: x, _, (a, b) for a pair, Point { x, y } for a Point. They're irrefutable.

Where a pattern appears decides which kind is allowed:

Place Allowed patterns Why
let, function parameters, for loops irrefutable only there's nowhere to go if the match fails
if let, while let, let ... else, match arms refutable they say what happens on a failed match (skip, stop, else, the next arm)

Try a refutable pattern in a plain let:

fn main() {
    let opt: Option<i32> = Some(3);
    let Some(x) = opt;
    println!("{x}");
}
error[E0005]: refutable pattern in local binding

The compiler even suggests the fix: let ... else, which you met in the enums module. The else block runs when the pattern fails and must leave the current block (return, break, continue or a panic), so code after it can rely on x existing.

let ... else for parsing

let ... else keeps the "happy path" flat when several steps can fail. This parses settings like port = 8080:

fn parse_setting(line: &str) -> Option<(String, u32)> {
    let Some((key, value)) = line.split_once('=') else {
        return None;
    };
    let Ok(number) = value.trim().parse::<u32>() else {
        return None;
    };
    Some((key.trim().to_string(), number))
}

fn main() {
    for line in ["port = 8080", "workers=4", "debug", "timeout = soon"] {
        match parse_setting(line) {
            Some((key, value)) => println!("{key} -> {value}"),
            None => println!("skipped: {line}"),
        }
    }
}
port -> 8080
workers -> 4
skipped: debug
skipped: timeout = soon

Irrefutable patterns you use all the time

fn print_score((name, points): (&str, u32)) {
    println!("{name} has {points} points");
}

fn main() {
    let scores = [("ana", 91), ("bo", 78)];
    for (name, points) in scores {
        print_score((name, points + 1));
    }
    let (best, _) = scores[0];
    println!("first listed: {best}");
}
ana has 92 points
bo has 79 points
first listed: ana

A function parameter can be a pattern ((name, points): (&str, u32)), and so can the variable of a for loop. Both are irrefutable: a pair always has two parts.

The opposite mistake, using if let with a pattern that can't fail (if let x = 5), compiles but warns irrefutable `if let` pattern: the if is pointless.

Your task

Write fn parse_point(text: &str) -> Option<(i32, i32)> that reads a point written as "x,y" (spaces around the numbers are allowed). Return None if there's no comma or either number doesn't parse (so "1,2,3" is None too: everything after the first comma must be one number). Use let ... else for each step that can fail.

Challenge 2

This program doesn't compile: let Some(percent) = ...; is a refutable pattern in a plain let. Fix it with let ... else so an unknown code gives no discount (the function returns the price unchanged). It should print 180, 150 and 200.

Challenge 3

Write a program that reads score lines like ana: 91. For each valid line print ana scored 91. Skip lines without a : or with a score that isn't a number, and at the end print skipped N lines. Trim spaces around the name and the score.

Use let ... else { continue; }; inside the loop for each step that can fail.

Previous: while let and slice patterns