Why non-exhaustive patterns in match?

I met this error in a place, which seems to grow more obscure and less scalable if rewritten. It comes in a way like this simplified example

enum TrafficLight { Green, Yellow, Red, White, Blue }

fn can_blink(l: TrafficLight, freq: usize) -> bool {
  if freq>0 {
    match l {
      TrafficLight::Green |
      TrafficLight::Red |
      TrafficLight::Blue => false,
      a => if freq<=1 {
        match a {
          TrafficLight::Yellow |
          TrafficLight::White => true,
        }
      } else { false },
    }
  } else { true }
}

fn main() {
    let s=if can_blink(TrafficLight::Yellow, 1) { "" } else { "not" };
    println!("Did you know, that: yellow traffic light can{} blink once per second", s);
}

Why do I get an error "non-exhaustive patterns"? I see that "match a" lacks three patterns or at least an "_" stub, but any of these would end up as dead code if introduced. It can be fixed with unreachable!() , but it would still hinder scalability. In my original code "else" arms also contain matches similar to "then" arms, and there is quite a bunch of patterns, and more complicated actions and result type other than bool, which makes it undesirable to move "if" anywhere else. Can I avoid more dead code, or is there a way for inner match to know which patterns are already covered by the outer one?

Maybe turn that if into a pattern guard? Something like this:

fn can_blink(l: TrafficLight, freq: usize) -> bool {
  if freq>0 {
    match l {
      TrafficLight::Green |
      TrafficLight::Red |
      TrafficLight::Blue => false,
      TrafficLight::Yellow |
      TrafficLight::White if freq<=1 => true,
      _ => false,
    }
  } else { true }
}

(Hmm, which could then be simplified further, it looks like...)

Of course, it can be simplified in this example. But, as I mentioned above, it is far more complex in real code, thus "if" and its inner match would rather be neither moved nor simplified. I just didn't devise any quick example to express this.

Like this?

fn can_blink(tl: TrafficLight, freq: usize) -> bool {
    use TrafficLight::*;
    match (tl, freq) {
        (_, 0) => true,
        (Green, _) | (Red, _) | (Blue, _) => false,
        (Yellow, 1) | (White, 1) => true,
        _ => false,
    }
}
1 Like

As there are several "shorten-em" replies, I have worked out a bit more complex example. Maybe this would be closer to original code (note: there are really far more match patterns than these several in the example below - so, please, do not shorten "match a" for Railroad with single "Some(1)", and do not substitute "match a" for Road with something as simple as "if let". It all works for this poor little example, but it wouldn't for big, mean and ugly real life function :slight_smile: ):

enum TrafficLight { Green, Yellow, Red, White, Blue }
enum TrafficWay { Railroad, Road }

fn max_freq(l: TrafficLight, way: TrafficWay) -> Option<usize> {
    match l {
        TrafficLight::Red => Some(0),
        TrafficLight::Yellow => Some(1),
        TrafficLight::Blue => if let TrafficWay::Railroad=way { Some(0) } else { None },
        a => if let TrafficWay::Railroad=way { 
            match a {
                TrafficLight::Green |
                TrafficLight::White => Some(1),
            }
        } else {
            match a {
                TrafficLight::Green => Some(0),
                TrafficLight::White => None,
            }
        },
    }
}

fn main() {
    let (s, i)={
        let f=max_freq(TrafficLight::Yellow, TrafficWay::Railroad).unwrap();
        (if f>0 { "" } else { "not" }, f)
    };
    println!("Did you know, that: yellow traffic light can{} blink {} time per second", s, i);
}

So, you stumble because in your example, all patterns are actually matched in all branches, but rustc still complains about non-exhaustive patterns?
Specifically in the code snipped

            match a {
                TrafficLight::Green |
                TrafficLight::White => Some(1),
            }

Imo, this is because rust is not a dependently typed programming language.
Haskell would issue the same warning, since a has still the type TrafficLight and not some intermediate type that can only be the values TrafficLight::Green or TrafficLight::White.
So, it makes actually sense and is way more predictable that you have to match in a match clause all patterns, independently of previous checks.
However, the compiler might be smart enough to optimise it, so just adding something like _ => None, shouldn't really hurt that much, and can be easily added, especially when you can return an Option.
You could also panic, since this situation should never arise, but some might consider this a harsh decision.

1 Like

I supposed something has to be added in inner matches, or the compiler wouldn't bother to be smart enough to limit "a" to something other than all the five-way enum which "a" comes from originally. The compiler would just mark the error, so the dead code line has to be added anyway to avoid it. Then it is up to compiler to optimize it or not.

Anyway, thanks for confirming the situation. It really seems like a 'language-dependent issue', with "a" being of the same type as "l". Suppose it can't be solved without introducing something like "enum slices",or better "enum subsets", which could limit a range for "a", thus making it not a TrafficLight, but a portion of its variants. Which, while derefing to TrafficLight, behaves a little different, allowing for the original code to compile.

It's a little annoying but I'd say the way it's done currently is probably the least bad solution.

It'd be convenient for the compiler to realise a particular variant is unreachable, but that means you can now no longer analyse a match statement in isolation and are required to bring along all the context of what is and isn't valid. Computers can do those sorts of things (in fact it's a key part of doing optimisation), but humans aren't as good at keeping things in their heads. Also, imagine trying to teach people how dependent types work... I've been using Rust for a couple years and have experience with other functional languages, and I barely understand it :stuck_out_tongue:

Usually I'd do something like this:

a => if freq <= 1 {
  match a {
    TrafficLight::Yellow | TrafficLight::White => true,
    _ => unreachable!(),
  }
}

That unreachable!() is arguably a lot more understandable at a glance than omitting the unreachable states and relying on the implicit knowledge that they are already handled.

2 Likes

unreachable!()

so far I do. Thanks