We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 2 of 24
Matching on Option
Write two functions that take Option<i32> apart by pattern matching.
pub fn classify(items: Vec<Option<i32>>) -> Vec<String>
pub fn first_pair(a: Option<i32>, b: Option<i32>) -> Option<i32>
classify labels each element: "none" for None, "zero" for Some(0),
"neg:{n}" for a negative value, "pos:{n}" for a positive one — so
[Some(1), Some(-2), Some(0), None] becomes
["pos:1", "neg:-2", "zero", "none"].
first_pair returns a‘s value, but only when both a and b carry one.
If either is None the answer is None. Note what this is not: it is not
a.or(b), and first_pair(None, Some(3)) is None, not Some(3).
The starter does not compile — that is the lesson
Run it before you change anything. You will get E0004:
error[E0004]: non-exhaustive patterns: `None` not covered
Option<i32> is an ordinary enum with two variants, and a match in Rust is
an expression that must produce a value on every path. The compiler counts
the variants you covered and stops you when one is missing. Guards do not
count — rustc deliberately does not reason about whether n > 0 and n < 0
together cover the integers, so Some(n) if n > 0 plus Some(n) if n < 0
still needs a guardless Some arm underneath.
This is the single most useful property of enums in Rust: add a variant
later, and every match that has not been updated becomes a compile error.
There is no equivalent for null. A language where absence is a value of the
same type gives you no place to hang that check.
The related error, E0005, appears when you use a refutable pattern where an
irrefutable one is required — let Some(n) = maybe; on its own is E0005,
“refutable pattern in local binding”. The fix is one of the forms below.
The four forms, and when each one reads best
// 1. match — the general form. Every arm visible, exhaustiveness checked.
match maybe {
Some(n) => println!("got {n}"),
None => println!("nothing"),
}
// 2. if let — one interesting arm, one boring one.
if let Some(n) = maybe {
println!("got {n}");
}
// 3. let-else — bind, or leave. The binding outlives the block.
let Some(n) = maybe else {
return "nothing".to_string();
};
println!("got {n}"); // `n` is in scope here, unindented
// 4. while let — loop until the pattern stops matching.
while let Some(top) = stack.pop() {
println!("{top}");
}
let-else (stable since 1.65) is the one beginners meet last and reach for
most once they know it. It is the fix for the rightward-drift you get from
nesting if let blocks: handle the failure and leave, then carry on at the
original indentation with the value bound.
Clippy has opinions about which form to use — single_match will tell you a
two-arm match with a () arm should be an if let, and
redundant_pattern_matching will tell you if let Some(_) = x should be
x.is_some(). These are translations between the four forms, so reading
clippy’s suggestion is how you learn the local convention.
A trap worth knowing now
while let Some(Ok(x)) = iter.next() { /* ... */ }
This silently stops at the first Err. The pattern Some(Ok(x)) fails to
match Some(Err(e)) exactly the way it fails to match None, and while let
cannot tell “the iterator ended” from “an item did not match”. Errors vanish
without a trace. This is the sort of bug that survives code review, so it is
worth recognising the shape on sight.
Notes
-
Match arms are tried top to bottom, so
Some(0)must come before a catch-allSome(n)or it will never be reached. -
Matching on a tuple of two Options —
match (a, b) { (Some(x), Some(_)) => …, _ => … }— is often clearer than nesting one match inside another, and it is howfirst_pairwants to be written. -
{n}insideformat!is inline captured-identifier syntax; it is the idiomatic form and clippy’suninlined_format_argspushes you towards it.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.