rust match in match: "spurious" non exhaustive error on simple enums: how to "propagate" the enum variable-kind knowledge?

Viewed 277

I hit an interesting spurious "match in match" non exhaustive error with rust. To make a small simple example (of course the production case is more interesting):

pub enum SomeEnum {
    Possibility1,
    Possibility2,
    Possibility3
}

pub fn do_something(some: &SomeEnum){
    match some {
        SomeEnum::Possibility1 => println!("this is possibility 1"),
        something_else => {
            println!("this is not possibility 1");

            match something_else {
                SomeEnum::Possibility2 => println!("but this is 2"),
                SomeEnum::Possibility3 => println!("but this is 3")
            }
        }
    }
}

does not compile, with an error:

error[E0004]: non-exhaustive patterns: `&Possibility1` not covered
  --> src/main.rs:13:19
   |
1  | / pub enum SomeEnum {
2  | |     Possibility1,
   | |     ------------ not covered
3  | |     Possibility2,
4  | |     Possibility3
5  | | }
   | |_- `SomeEnum` defined here
...
13 |               match something_else {
   |                     ^^^^^^^^^^^^^^ pattern `&Possibility1` not covered
   |
   = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
   = note: the matched value is of type `&SomeEnum`

I understand this error, and I know how to "brutally" fix it (similarly to Non-exhaustive patterns - Rust match expressions by adding a:

_ => unreachable!();

branch for example).

Still, I feel this is very sad: the compiler is smart enough at the first match to know that the something_else is either of kind Possibility2 or Possibility3, while toss away this knowledge down the code?

My question is therefore: is there a simple, idiomatic way to force rust to "propagate" this kind knowledge from the outer match to the inner match so that I can suppress the error without brutally matching all inputs? Again, in this simple example the _ match may seem like the no-brainer, but in some more complex code, I would like to have help from the compiler checking that my logics are right, and this disqualifies using a _ match-it-all filter.

1 Answers

Still, I feel this is very sad: the compiler is smart enough at the first match to know that the something_else is either of kind Possibility2 or Possibility3

It's not, really. Semantically it just tries every match arm one after the other and takes the first.

My question is therefore: is there a simple, idiomatic way to force rust to "propagate" this kind knowledge from the outer match to the inner match

No. rustc reasons at the type level (mostly), as far as it's concerned something_else is a SomeEnum, and SomeEnum has a SomeEnum::Possibility1. If you want a sub-enum, you have to provide one.

This sort of things might eventually possibly be workable if RFC 2593 Enum Variant Types (or an alternative like OCaml-style polymorphic variants) gets tabled again and accepted, and implemented. But short of that you'll have to implement it "by hand" on your enum.

By "do it by hand" I mean manually encode the semantics you want as additional enums e.g.

pub enum SomeEnum {
    Possibility1,
    Possibility2,
    Possibility3
}

enum IsOne { One, Other(Sub23) }
enum Sub23 {
    Sub2,
    Sub3
}
impl SomeEnum {
    fn is_one(&self) -> IsOne {
        match self {
            Self::Possibility1 => IsOne::One,
            Self::Possibility2 => IsOne::Other(Sub23::Sub2),
            Self::Possibility3 => IsOne::Other(Sub23::Sub3),
        }
    }
}

pub fn do_something(some: &SomeEnum){
    match some.is_one() {
        IsOne::One => println!("this is possibility 1"),
        IsOne::Other(sub) => {
            println!("this is not possibility 1");

            match sub {
                Sub23::Sub2 => println!("but this is 2"),
                Sub23::Sub3 => println!("but this is 3")
            }
        }
    }
}
Related