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
line.split_once('=')splits at the first=and returnsSome((before, after)), orNoneif there's no=(more string tools in module 18).let Some((key, value)) = ... else { return None; };destructures two levels at once: theOptionand the tuple inside it.parsereturns aResult, so the second step useslet Ok(number) = ... else.
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.