We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Shape Your Data: Structs, Enums, Pattern Matching step 13 of 26
Match guards, and why they don't count toward exhaustiveness
Grade a list of (subject, score) pairs. The pass mark depends on the
subject: maths needs 70, art needs 50, everything else needs 60. A negative
score is invalid data.
pub fn grade(scores: Vec<(String, i64)>) -> Vec<String>
Each entry renders as "{subject}:{verdict}" where the verdict is pass,
fail or invalid. ("maths", 70) is "maths:pass"; ("maths", 69) is
"maths:fail"; ("art", 69) is "art:pass".
Why a guard and not a range pattern
You met range patterns two items ago, and they look like the answer here.
They are not, and the reason is precise: the bounds of a range pattern must
be constants known at compile time. The threshold in this problem is a
value computed at run time from the subject name, so required.. is not
something you can write.
That is exactly the gap a guard fills:
match score {
points if points < 0 => "invalid",
points if points >= required => "pass",
_ => "fail",
}
A guard is an ordinary if bolted to an arm, evaluated after the pattern
matches and with the pattern’s bindings in scope. If the guard is false, the
arm is skipped and matching continues with the next arm — this is not an
early exit.
The rule that catches everyone
Guards do not count toward exhaustiveness.
match n {
x if x > 0 => 1,
x if x <= 0 => 0,
}
Between them those two guards cover every i64 in existence. rustc rejects
it anyway:
error[E0004]: non-exhaustive patterns: `i64::MIN..=i64::MAX` not covered
The starter ships that error. It is not a limitation to route around, it is the compiler being honest about what it can prove. A guard is arbitrary Rust — it can call functions, read variables, do arithmetic. Deciding whether two such expressions are jointly total is undecidable in general, so the exhaustiveness checker does the only sound thing available: it assumes every guard might be false. The line between “the compiler proved this” and “the compiler trusted you” runs exactly here, and it is worth knowing which side of it you are standing on.
The fix is a final unguarded arm. Some case in the test suite reaches it.
Three more facts about guards
A guard applies to the whole arm, including every alternative of an
or-pattern. A(n) | B(n) if n > 0 guards both, not just B. If you meant
otherwise, parenthesise or split the arm.
Guards cannot mutate the scrutinee. E0301, “cannot mutably borrow in a
pattern guard” — the value being matched is borrowed for the duration of the
match, and a guard that changed it could invalidate the very pattern that
just matched.
If-let guards are stable. Some(x) if let Some(n) = x.checked_add(1) => ...
became stable in Rust 1.95. Plenty of material online still calls this
unstable; it is not.
The lint that pushes work into patterns
redundant_guards is on by default and rewrites this:
Some(x) if x == 5 => ... // becomes Some(5) => ...
The direction is right. Anything the pattern can express belongs in the
pattern, where the exhaustiveness checker can see it; the guard is for what
is left over. match_same_arms and equatable_if_let live in the same
neighbourhood, both allow-by-default.
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.