Non-exhaustive pattern when using extra conditionals

When I try to run the following code:

let num = Some(5);

match num {
    Some(x) if x < 5 => println!("{} < 5", x),
    Some(x) if x > 5 => println!("{} > 5", x),
    Some(5)  => println!("5"),
    None => println!("None"),

}

I get a compiler error because my patterns are non exhaustive:

non-exhaustive patterns: Some(i32::MIN..=4_i32) and Some(6_i32..=i32::MAX) not covered

I don't really understand what I'm missing, it seems to me that all cases are covered:

  1. Some(x) with x < 5
  2. Some(x) with x > 5
  3. Some(x) with x = 5
  4. None

But from what I understand from the error message, rust thinks I'm not covering cases 1 and 2.

Why are you using Some(_) and None to match a i32?

The compiler is just nor smart enough to figure out you did indeed cover all bases. But with some reordering we can get the compiler to aceept it:

match num {
    Some(5)          => println!("5"),
    Some(x) if x < 5 => println!("{} < 5", x),
    Some(x)          => println!("{} > 5", x),
    None               => println!("None"),
}

The trick here is that the Some(x) pattern covers all cases that have not been matched before, which in this case would be x>5.

Sorry, I made a mistake when copying, the code I just edited the question.

I'm actually using Some(5) but I made a mistake when copying the code in the question, I just edited it, sorry.

The compiler does not attempt to understand if guards applying numeric range restrictions. It does understand range patterns (as long as they're ..= closed ranges rather than .. half-open ranges) and this code will be accepted without needing any catch-all condition:

    match num {
        Some(x @ i32::MIN..=4) => println!("{} < 5", x),
        Some(x @ 6..=i32::MAX) => println!("{} > 5", x),
        Some(5) => println!("5"),
        None => println!("None"),
    }

Playground

The compiler's error message hinted at this by the syntax it used for reporting what was not covered.

4 Likes

It probably cannot, at least not for all types. Take for instance the case of floating point numbers, where all of a<b, a>b and a==b can be false.

To get the desired exhaustiveness here, match on the result of calling Ord::cmp:

match num.map(|n| n.cmp(&5)) {
    Some(Ordering::Less) => /* ... */,
    Some(Ordering::Equal) => /* ... */,
    Some(Ordering::Greater) => /* ... */,
    None => /* ... */,
}
5 Likes

I see, is the compiler, in general, unable to check how exhaustive extra conditionals are?

I tried the following example and it has the same problem:

let letter = Some('a');

match letter {
    Some(x) if x == 'a' => println!("a"),
    Some(x) if x != 'a' => println!("not a"),
    None => println!("None"),

}

It does not attempt to check how exhaustive conditional guards are at all because this is how the compiler is implemented. Even though your example might be easy to check, checking exhaustiveness is not an easy work in general.

Even if it checking the exhaustiveness of conditional guards were implemented, it could only be done for type the compiler knows implement Eq or Ord correctly. Consider this made-up example:

struct Silly(i32);

impl PartialEq for Silly
{
    fn eq(&self, _: &Self) -> bool
    {
        false
    }
    fn ne(&self, _: &Self) -> bool
    {
        false
    }
}

fn cmp(num: Option<Silly>)
{
    match num
    {
        Some(x) if x == Silly(3) => println!("Equal!"),
        Some(x) if x != Silly(3) => println!("Not Equal!"),
        Some(_)                  => println!("Math fails!"),
        None                     => println!("None")
    }
}

fn main()
{
    let num = Some(Silly(2));
    cmp(num);
}

In this case the first two branches don't exhaust the range of values for Silly.

Correct, because they can be arbitrarily complicated. Rust is pretty consistent about this; you'll see that fn foo() -> i32 { if true { return 4 }; } also fails to compile.

If you want exhaustiveness, you have to phrase it as patterns, not conditionals.

For example, you could write

match letter {
    Some('a') => println!("a"),
    Some(_) => println!("not a"),
    None => println!("None"),
}

since match arms are tried in order.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.