What is going on with this. a % 2 can only be 0 or 1.
fn main() {
let a:usize = 26;
match a % 2 {
0 => println!("even"),
1 => println!("odd"),
}
}
Clippy says:
error[E0004]: non-exhaustive patterns: `2_usize..` not covered
--> src/main.rs:3:11
|
3 | match a % 2 {
| ^^^^^ pattern `2_usize..` not covered
|
= note: the matched value is of type `usize`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
I am concerned I am missing something.
Adding a match arm: _ => unreachable!("Not odd or even"), fixes the clippy error, but there is no actual error. Or am I missing something?
That's not clippy, that's rustc's error. match exhaustiveness is based on types, and the knowledge that % 2 returns 0 or 1 is not part of the type system. So you need the unreachable branch. The optimizer will be able to tell this branch is unreachable and remove it.
Checking of exhaustiveness of matches is done entirely based on the type of what is being matched. The type is usize, so you must provide patterns that together cover all of usize.
It’s necessary for the compiler to be very precise about what it allows here, because if it tried to use arbitrary information to decide that additional arms weren't necessary, then some change to how it does the analysis might cause your code to stop compiling in future Rust versions. To avoid that kind of fragility, rules that decide whether code is valid have to be carefully scoped. Note that the places where Rust has provided powerful tools to “do the right thing” given just a little input — type inference and traits — are places where Rust moves slowly. It’s difficult to work on those things without breaking existing programs.
There are ideas for how Rust could gain “pattern types”, subsets of existing types, and if that happened, then there could be a new remainder function (probably not changing the existing operator) whose return type was "usize, but only 0..2". That would be a precisely defined way to avoid needing a third match arm here.
Why wouldn't it? If the compiler supported this for n % 2 should it support it for more complex operations too? And where should it stop given this is an undecidable problem?
I think llvm will optimize away the unreachable branch; it "knows" that unsigned modular reduction only takes those values. It's one of many places where the Rust language forces you to write something that the optimizer can deduce is unnecessary.
fn frobnicate(n: usize) -> usize {
// I'll only ever return 0 or 1... Promise
}
match frobnicate(n) {
0 => {},
1 => {},
// Why do I need this extra arm?
_ => {},
}
Sure it might (or might not) seem obvious that you only have 0 or 1 as possibilities, but without more information it's impossible to know.
This is basically the compiler's view of the situation. Our argument is essentially reduced to "trust me bro" and the compiler doesn't trust us
Personally, I like the trade-off that Rust has made where I have to type that if it's not clear from the types involved, because I can use my own types to tweak it. I much prefer it to other languages that either silently do nothing (_ => {}) or silently throw if you didn't handle something, even if sometimes it means writing unreachable!().