Search code examples
rustpattern-matchingmodular-arithmetic

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


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?


Solution

  • 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.