Match doesn't seem to work exhaustively


#1

match doesn’t make a compile error for unknown value or non-exaustive matches. Is this (1) my fault, (2) a feature or (3) a bug?

fn main() {
    #[derive(Debug)]
    enum Foo1 {
        bar2,
        bar3,
        bar4,
    };
    let a = Foo1::bar2;
    match a {
        x => {
            println!("{:?}", x);
        },
    };
}

On Playground


#2

The answer is (1).
The pattern x matches any possible value of a, and so the statement println!("{:?}", x); is surely executed. The exhaustion rule means that it is forbidden a situation in which no arm matches the expression.


#3

Thanks!

Although I see the use case of this feature, this seems to be very error-prone to me… Anyway I’ll try to change my mind.


#4

Why do you think it’s error-prone? Could you show one or more errors caused by it?


#5

I mean “error-prone” it does not help to prevent my (programmer) errors. Because arbitrary typing mistake can be treated as valid input of “match-any” regardless of programmer’s intention. Here’s another example.

enum Foo1 { bar2, bar3, bar4 }
use Foo1::*;

fn main() {
    let a = bar2;
    let b = match a {
        bar2 => "a",
        bar => "b",
    };
    println!("{}", b);   
}

Programmer made several mistake here, and this kind of error is usually happen for me. Anyway, I discovered that Rust compiler warns about unused variable bar in this case, so I think this wouldn’t be a big problem as like I imagined.


#6

Rust also has a very useful error if you use a variant name as the identifier.

enum X { A, B, C }

fn main() {
    let x = X::A;
    
    println!("{}", match x {
        A => {
            let _ = A;
            "a"
        },
    });
}
warning[E0170]: pattern binding `A` is named the same as one of the variants of the type `X`
 --> src/main.rs:7:9
  |
7 |         A => {
  |         ^
  |
  = help: if you meant to match on a variant, consider making the path in the pattern qualified: `X::A`

#7

This is partly why enum variants are written in CapitalCamel case, and variables are written in lower_snake case. This is also why it’s a good idea to (where reasonable), avoid doing use Foo::*;, and instead writing Foo::Bar2, etc.. Finally, I’d be stunned if there wasn’t a clippy lint to protect against this. Also, this is only an issue if you make this mistake on the last arm of a match; doing this on a previous one should trigger an “unreachable arm” error.

It’s not ideal, no, but the odds of actually making this mistake in practice while writing Rust code idiomatically seems pretty low.


#8

In addition, if a variable is used as pattern, and such variable is not used in the right side of the arm, an unused variable warning is issued.


#9

Incidentally, you use this feature of pattern matching every time you create a new variable; let takes a pattern. names like this are irrefutable patterns, as they always match. let x = 5 is doing the same thing as the match arm.