Why is `2_u32..=u32::MAX` not covered when matching on `u64 % 2`?
Asked Answered
B

1

5

If I try to build the following code:

fn main () {
    let my_val: u32 = 42;
    
    match my_val % 2 {
        0 => println!("We're even now"),
        1 => println!("Well, that's odd"),
    }
}

I will have the following error message:

error[E0004]: non-exhaustive patterns: `2_u32..=u32::MAX` not covered
 --> src/main.rs:4:11
  |
4 |     match my_val % 2 {
  |           ^^^^^^^^^^ pattern `2_u32..=u32::MAX` not covered
  |
  = note: the matched value is of type `u32`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
  |
6 ~         1 => println!("Well, that's odd"),
7 ~         2_u32..=u32::MAX => todo!(),
  |

I don't really get it. What is the case represented by 2_u32..=u32::MAX?

Berl answered 11/9 at 9:11 Comment(4)
doc.rust-lang.org/rust-by-example/flow_control/match.html all possible values must be covered and the compiler assumes u32 for the return value of % since that's the type of its operand. Add a case arm _ => unreachable!(), to silence the error in the quickest way possible or you should invent a strategy that will have type safety while performing that operation. This may sound like an answer given on comments.. but it's actually a workaroundDeeply
What the case is: any number >= 2 (which % would be allowed to produce in theory, even though in practice it won't, given its arguments) - You covered only 0 and 1 but the result is a u32 which has 4294967294 more possible values you don't yet cover. Solution: add a _ case.Ethno
You are basically asking for dependently typed functions (if arg to % is n, return a UintAtMost<n-1>), which Rust doesn't and likely won't ever have.Pamper
One thing that was confusing to me, is that I didn't understand that 2_u32..=u32::MAX means between 2 and max value of an u32Berl
O
7

This is really simple. % operator is defined using std::ops::Rem trait. Its associated type Output defines what is the result for that operation. In implementation for u32 Output is u32. So from the perspective of the type system Rem::rem can return any u32 value. Therefore for match to be exhaustive you must match all of the values.

You can use unreachable macro (which will produce a panic at runtime if it is ever reached), to indicate that those paths are, well unreachable.

pub fn foo(x: u32) {
    match x % 2 {
        0 => println!("We're even now"),
        1 => println!("Well, that's odd"),
        _ => unreachable!("u32 mod 2 can only return 0 or 1"),
    }
}

Note that this third branch is very easy to optimize out, and that compiler will do this in the release builds. You can examine produced ASM using goldbolt.

If you wonder, why do I have to do this, or why cannot Rust just let me pass this one time, note that this is an example of general Rust's philosophy of expecting explicitness. Mentioning that other branches are unreachable will result in no or negligible runtime cost (and you can always go into unsafe realm if you really need it), and can catch possible future bugs. If for some example you start using mod 3 instead of mod 2 arithmetic, or you pass this value from some external, unverified source, than your code will still compile, but this bug will be caught at runtime.

Oestriol answered 11/9 at 9:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.