Enum match arm inference?

Hey there,

I was wondering why the compiler can't infer the "namespace" of a match arm when matching Enums. My intuition makes me believe that since it knows what type its matching against that it could assume that the options it gets are variants of the enum, so the example bellow could be compiled.

Is there are reason that this cannot be done ?

enum Foo {
    Value,
    AnotherValue
}

fn print_foo(foo: Foo) {
    match foo {
        Value => println!("avalue"),
        AnotherValue => println!("anothervalue"),
    }
}

I mean, you could change the language so that enum variants are automatically in scope when matching on them. But it would be an exception to the rules for when things are in scope, and the language doesn't have any other exceptions of that kind at all. Not having exceptions to the rules usually makes the language simpler.

But you can do this:

enum Foo {
    Value,
    AnotherValue
}

fn print_foo(foo: Foo) {
    use Foo::*;
    match foo {
        Value => println!("avalue"),
        AnotherValue => println!("anothervalue"),
    }
}
3 Likes

because patterns are complicated, they are not just enum variants, and it will cause ambiguity. particularly in your example, a single identifier is a treated as a variable binding. (rust doesn't distinguish upper case identifiers from lower case identifiers, unlike certain languages like Haskell, where the upper case identifier is not a variable).

e.g. the following code is perfectly valid:
EDIT:
you'll actually get an E0170 diagnosis if you bind to a variable with the same name of the variant, but the idea of being ambiguous still holds, to quote the documentation for E0170:

If you don't qualify the names, the code will bind new variables named "GET" and "POST" instead. This behavior is likely not what you want, so rustc warns when that happens.

enum Foo {
    Value,
    AnotherValue
}
fn foo(foo: Foo) {
    match foo {
        Foo::Value => println!("Foo::Value"),
        // EDIT:
        // use a variable named `Value` triggers E0170
        /*
        Value => println!("bind to a variable named `Value`"),
        */

        // you can still bind to a variable named `Value`,
        // just the "simple variable" pattern is checked for E0170
        Value@_ => println!("bind to a variable named `Value`"),
        // you can also bind a reference
        ref Value => println!("bind ref to variable named `Value`"),
    }
}
2 Likes

So maybe I'm not understanding how match works. If I'm matching against an Enum type, how can it expect a completely unrelated variable ? The possibilities are known, so much that it can check if you handled them all.

It’s not an already existing variable, it’s a new variable that is only accessible to the specific match branch. This is equivalent:

enum Foo {
    Value,
    AnotherValue
}
fn foo(foo: Foo) {
    match foo {
        Foo::Value => println!("Foo::Value"),
        my_var => println!("bind to a variable named `my_var` with value {:?}", my_var),
    }
}

However, I am a big fan of the following extension to the language:

enum Foo {
    Value,
    AnotherValue
}

fn print_foo(foo: Foo) {
    match foo {
        _::Value => println!("avalue"),
        _::AnotherValue => println!("anothervalue"),
    }
}

Which I believe would solve the variable ambiguity…

3 Likes

as I said, patterns are complicated.

there are different kinds of patterns, destructured enum is just one of them.

also, there are many places where a pattern can be matched, match arms is just one of them.

most notably, a let binding is actually pattern matching (just the pattern must be non-refutable). likewise if let or while let can be used to match refutable patterns. function parameters (e.g. see this discussion) are pattern matching too.

1 Like

Thanks, I hadn't realized you could just bind whatever value to a variable like that. Now a lot of things make more sense ahahaha

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.