rustpattern-matchingnon-exhaustive-patterns

How do I relax the non-exhaustive patterns check for a nested match on known variants?


How do I persuade the Rust compiler that the internal match expression is fine here, as the outer match has already restricted the possible types?

enum Op {
    LoadX,
    LoadY,
    Add,
}

fn test(o: Op) {
    match o {
        Op::LoadX | Op::LoadY => {
            // do something common with them for code reuse:
            print!("Loading ");

            // do something specific to each case:
            match o {
                // now I know that `o` can only be LoadX | LoadY,
                // but how to persuade the compiler?
                Op::LoadX => print!("x"), /* LoadX specific */
                Op::LoadY => print!("y"), /* LoadY specific */
                _ => panic!("shouldn't happen!"),
            }

            println!("...");
        }

        Op::Add => println!("Adding"),
    }
}

fn main() {
    test(Op::LoadX);
    test(Op::LoadY);
    test(Op::Add);
}

I tried two approaches, but neither seems to work.

  1. Name the or-pattern and then match using that name:

    match o {
        load@(Op::LoadX | Op::LoadY) => {
        // ...
        match load {
            // ...
        }
    } 
    

    That's not valid Rust syntax.

  2. Name and bind every constructor:

    match o {
        load@Op::LoadX | load@Op::LoadY => {
        // ...
        match load {
           //...
        }
    } 
    

    That still doesn't satisfy the exhaustiveness check, hence the same error message:

    error[E0004]: non-exhaustive patterns: `Add` not covered
      --> src/main.rs:14:19
       |
    14 |             match load {
       |                   ^ pattern `Add` not covered
    
    

Is there any idiomatic way of solving this problem or should I just put panic!("shouldn't happen") all over the place or restructure the code?

Rust playground link


Solution

  • You cannot. Conceptually, nothing prevents you from doing o = Op::Add between the outer match and the inner match. It's totally possible for the variant to change between the two matches.

    I'd probably follow Stargateur's code, but if you didn't want to restructure your enum, remember that there are multiple techniques of abstraction in Rust. For example, functions are pretty good for reusing code, and closures (or traits) are good for customization of logic.

    enum Op {
        LoadX,
        LoadY,
        Add,
    }
    
    fn load<R>(f: impl FnOnce() -> R) {
        print!("Loading ");
        f();
        println!("...");
    }
    
    fn test(o: Op) {
        match o {
            Op::LoadX => load(|| print!("x")),
            Op::LoadY => load(|| print!("y")),
            Op::Add => println!("Adding"),
        }
    }
    
    fn main() {
        test(Op::LoadX);
        test(Op::LoadY);
        test(Op::Add);
    }
    

    should I just put panic!("shouldn't happen")

    You should use unreachable! instead of panic! as it's more semantically correct to the programmer.