Reading the "The Rust Programming Language"- part "18. Patterns and Matching", chapter "18.3. Pattern Syntax". Towards the end of the chapter, section "@ Bindings" introduces a way to "create a variable that holds a value at the same time we’re testing that value to see whether it matches a pattern". However, I’m struggling to see what value this adds over the approach discussed in section "Extra Conditionals with Match Guards" earlier in the chapter.
For example, taking the "@ Bindings" section’s code example, below I believe I’ve implemented the same logic in both ways, hence I’m not seeing the value of the "@ Bindings" approach.
enum Message {
Hello { id: i32},
}
fn main() {
let msg = Message::Hello { id: 5 };
// @ Binding Operator Example
match msg {
Message::Hello { id: idvar @ 3..=7 } => println!("Found an id in range: {}", idvar),
Message::Hello { id: 10..=12 } => println!("Found an id in another range"),
Message::Hello { id } => println!("Found some other id: {}", id),
}
// Conditional Match Guard Example
match msg {
Message::Hello { id: idvar } if (3..=7).contains(&idvar) => println!("Found an id of range: {}", idvar),
Message::Hello { id: idvar } if (idvar >= 10 && idvar <=12) => println!("Found an id in another range"),
Message::Hello { id: idvar } => println!("Found some other id: {}", idvar),
}
}
Note: I’m just using two different approaches in the match guard to define q range as an experiment – not directly relevant to the question
I’m sure I’m missing some subtle benefits (maybe not addressed by the simplified example provided in the book). What unique benefits does the @ operator binding approach provide or is it just an alternative approach?
>Solution :
The main difference is exhaustiveness checking. Match guards can’t contribute to exhaustiveness checks in the compiler, so additional cases may be required which could be avoided using bindings. Here’s an example:
let n = 5u8;
// this works: the compiler can tell that together, each arm covers all
// possible cases
match n {
value @ 0..=127 => println!("low: {}", n),
value @ 127..=255 => println!("high: {}", n),
}
// this doesn't work: match guards don't contribute to exhaustiveness
// checking, so the compiler isn't sure that all possible cases are covered
// and requires a default case (even though it's unreachable)
match n {
value if (0..=127).contains(&value) => println!("low: {}", n),
value if (127..=255).contains(&value) => println!("high: {}", n),
// uncommenting this line will allow it to compile
// _ => unreachable!()
}