Step 2 of 6
Match guards and @ bindings
A plain name in a pattern is a binding: it matches any value and gives it that name inside the arm. On its own that's not very selective, so you can add a match guard: if condition after the pattern. The arm only runs when the pattern matches and the condition is true.
fn describe(balance: i64, overdraft_limit: i64) -> String {
match balance {
0 => String::from("empty"),
b if b < -overdraft_limit => format!("{b}: over the limit!"),
b if b < 0 => format!("{b}: overdrawn"),
b => format!("{b}: in credit"),
}
}
fn main() {
println!("{}", describe(0, 100));
println!("{}", describe(-250, 100));
println!("{}", describe(-40, 100));
println!("{}", describe(900, 100));
}
empty
-250: over the limit!
-40: overdrawn
900: in credit
b if b < -overdraft_limitbinds the value tob, then checks the guard. A guard can use any expression, including other variables likeoverdraft_limit. Patterns alone can only contain fixed values, so guards are how you compare with a variable.- The arms are tried in order, so the second guard only sees balances that aren't over the limit.
- The last arm
bhas no guard: it matches everything left, like_, but keeps the value.
@ bindings: test a pattern and keep the value
name @ pattern checks the value against the pattern and binds it to name. It shines when the value is buried inside something, like a number inside Some:
fn main() {
let readings = [Some(3), Some(42), Some(-1), Some(250), None];
for r in readings {
let text = match r {
Some(n @ 0..=9) => format!("one digit: {n}"),
Some(n @ 10..=99) => format!("two digits: {n}"),
Some(n) if n < 0 => format!("sensor error {n}"),
Some(n) => format!("large: {n}"),
None => String::from("no reading"),
};
println!("{text}");
}
}
one digit: 3
two digits: 42
sensor error -1
large: 250
no reading
Without @ you'd have to write Some(n) if n >= 0 && n <= 9, which is longer and easier to get wrong.
With |, a guard applies to all the alternatives: 1 | 2 if urgent => means "(1 or 2) and urgent".
Guards don't count for exhaustiveness
The compiler checks patterns, but it doesn't try to prove things about your guard conditions. To it, these two arms might both be false:
fn main() {
let n: i32 = 7;
let sign = match n {
x if x < 0 => "negative",
x if x >= 0 => "zero or positive",
};
println!("{sign}");
}
error[E0004]: non-exhaustive patterns: `i32::MIN..=i32::MAX` not covered
The fix is to make the last arm unguarded: _ => "zero or positive".
Common mistakes
A name where you meant a comparison. This doesn't check
guess == target;targetis a brand-new variable that matches anything, so the first arm always runs (the compiler warns about it):match guess { target => println!("correct!"), _ => println!("try again"), }Write
g if g == target => ...instead.Guarding every arm. End with an unguarded arm so the match is exhaustive.
Your task
Write fn speed_check(speed: u32, limit: u32) -> String using a match on speed with guards:
- at or under the limit:
"ok" - up to 10 km/h over:
"warning: N over"(for example"warning: 7 over") - more than 10 over:
"fine: N over"
Challenge 2
Fill in the blank with an @ binding so the first arm catches 1 to 5 tickets left and names the number n.
Challenge 3
A smart thermostat reports Option<i32> (degrees Celsius, None when the sensor is offline). Write fn room_status(temp: Option<i32>) -> String:
None:"sensor offline"- 18 to 24 inclusive:
"comfortable at T"(use an@binding) - below 18:
"too cold at T"(use a guard) - anything else:
"too hot at T"
Previous: Literals, ranges and | Next: Destructuring structs and tuples