Why aren't enum variant names auto imported into the `match` scope?

Suppose you have an enum:

enum Enum {
  Foo,
  Bar,
}
let x : Enum = ...;

and you want to match on x. By default, you either use qualified variant name,

match x {
  Enum::Foo => ...,
  Enum::Bar => ...,
}

or you import those names into the current scope,

use Enum::*;
match x {
  Foo => ...,
  Bar => ...,
}

But I do not see why those names are not being imported automatically only in the match's scope. That is, allow this to work by default:

match x {
  Foo => ...,
  Bar => ...,
}

And the scope outside the match is not changed. This is both cleaner and more convenient.

My question is why it doesn't work this way? Is there something I missed? Thanks.

There has been a lengthy discussion on the internals forum five years ago:

I don't think it resulted in an RFC. From a glance at the discussion, there are people in favour of this, so it might be worth pursuing.

1 Like

Sometimes I miss Pascal's with (not exactly the same, but the same idea). You can include that in a block, if you really don't want to see the entire path:

let y = {
    use Enum::*;
    match x {
        First => 1,
        Second => 2,
    }
};

It would even be possible to create a with macro for it.

But since the IDE creates the pattern, I usually don't mind seeing the whole path. And if I do mind it there, chances are I'll mind it elsewhere, too, so I'm adding a use for the entire scope.

1 Like

Thanks for the pointer! I see there are many good points made there.

Now that I thought more about it, I can think of some new arguments in favor of / against this feature:

This feature more-or-less lets the type in context decide what symbols are in scope (if you squint hard enough). That is, each match arm (if is a binding) must be of type of the scrutinee.

Now, consider other type-sensitive contexts:

let x: Enum = Foo; // Should be allowed or disallowed?

and matches!

matches!(x, Foo); // assuming `x`'s type is known, should be allowed or disallowed? 

I don't think it's a good idea to allow them. But, if we disallow them, these do seem a bit "out of place". Personally, I'm happy to have the convenience at the cost of this discrepancy though.

I think that just the name of the enum is too subtle.

Note that we do, at least, now how a dedicated deny-by-default lint if you forgot to import or qualify the name https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=a72daec225d3f91d7ea9353609b2e1ee

error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `Enum`
 --> src/lib.rs:7:3
  |
7 |   Foo => "a",
  |   ^^^ help: to match on the variant, qualify the path: `Enum::Foo`
  |
  = note: `#[deny(bindings_with_variant_name)]` on by default

error[E0170]: pattern binding `Bar` is named the same as one of the variants of the type `Enum`
 --> src/lib.rs:8:3
  |
8 |   Bar => "b",
  |   ^^^ help: to match on the variant, qualify the path: `Enum::Bar`

Hopefully your IDE has a convenient way to apply that help.


What I think is plausible is something like swift where there's a syntactic opt-in for "look, I want a variant of whatever type this is".

Thus you'd be able to write, say,

match x {
  .Foo => ...,
  .Bar => ...,
}

and have it be clear that you're using a variant.

That would similarly apply in other contexts too, like

Options { algorithm: .Sha256, .. }
3 Likes