Possible to match on match arm expression

Is it possible to match on the arm expression variants only in a submatch, as seen in the code comments below? I have a few instances of this pattern and would love to avoid having to add a _ => {} to the match statements even though I know it is useless.

Thanks in advance!

enum SomeOptions {
    A,
    B,
    C,
    D,
    E,
    F
    
}

fn main () {

let test = SomeOptions::A;
match test {
    SomeOptions::A => {println!("A")},
    SomeOptions::B | SomeOptions::C => {
        // do complex things here that happen for either B or C
        println!("B or C");
        // I really want to match on the subset of options present
        // in the match arm expression, but AFAIK I have to match
        // on the outer variable again
        match test {
            SomeOptions::B => {println!("B")},
            SomeOptions::C => {println!("C")},
            // don't need to check for other options here,
            // as the outer match is already doing that for us
            // this is useless here
            _ => {}
            
        }
        
    }
    SomeOptions::D => {println!("D")},
    // ignore other options for now.
    _ => {}
    
}
}

Playground link with same code here Rust Playground

What is the purpose of the inner match, precisely?

The presumed reason for using an | pattern is because that branch uses the same logic for both patterns. But the code you share doesn't do that; it combines flow control briefly and then splits again with a second match. In other words, the sample code is equivalent to:

if let SomeOptions::B | SomeOptions::C = &test {
    println!("B or C");
}
match test {
    SomeOptions::A => println!("A"),
    SomeOptions::B => println!("B"),
    SomeOptions::C => println!("C"),
    SomeOptions::D => println!("D"),
    _ => (),
}

The other option is using unreachable!() or unreachable_unchecked() in the catch-all arm on the inner match. The catch-all is required by the language. It's a feature that prevents logic errors.

1 Like

The | pattern on the branch is used to have logic that runs when either of the 2 conditions happens, and then also run unique logic against both of the 2 conditions at the end of the block.

As a more concrete example, I have an application where I use the same form for editing and creating. The only difference is when displaying the name of the object, and when saving.

It seems like the compiler isn't smart enough to recognize that the match pattern is already restricted by the previous condition, and let me match on the sub-pattern defined by the outer match arm.

Right, Rust matching does not have that feature.

1 Like

I'm not sure what that would even look like. You can get close, sort of, by switching the inner match to if-let-else-let.

match test {
    SomeOptions::A => println!("A"),
    SomeOptions::B | SomeOptions::C => {
        println!("B or C");
        if let SomeOptions::B = test {
            println!("B");
        } else if let SomeOptions::C = test {
            println!("C");
        }
    }
    SomeOptions::D => println!("D"),
    _ => (),
}
1 Like

I would make another function:

fn b_or_c(f: impl FnOnce()) {
    println!("B or C");
    f();
}

match test {
    SomeOptions::A => println!("A"),
    SomeOptions::B => b_or_c(|| println!("B")),
    SomeOptions::C => b_or_c(|| println!("C")),
    SomeOptions::D => println!("D"),
    _ => {}
}

This is nice because it's clear that:

  1. Each match arm gets the entirety of the code, once the functions are inlined.
  2. There's definitely no extra branching happening within the arms.
1 Like

Another option is to use a nested enum:

enum InnerOption { B,C };
enum SomeOptions {
    A,
    BorC(InnerOption),
    D,
    E,
    F
}

// …

match test {
    SomeOptions::A => {println!("A")},
    SomeOptions::BorC(inner) => {
        println!("B or C");
        match inner {
            InnerOption::B => {println!("B")},
            InnerOption::C => {println!("C")},
        }
    }
    SomeOptions::D => {println!("D")},
    _ => {}
}
1 Like

The lack of branching is something I hadn't thought about. Self taught on all this mess so often don't think about things like minimizing branching.