Bring enum variants in scope for patterns

I think aesthetical concerns should be left out in this discussion, because

  • they are subjective
  • everything unfamiliar looks weird at first, until you get used to it.

I'd like to summarize some of the pros and cons. The options are

  1. don't change anything (write use Foo::* to bring variants into scope)
  2. automatically bring variants of the correct enum into scope
  3. Allow substituting the enum name with _

If you disagree with something on this list, I can edit this comment.

Convenience

With proper IDE support, all three options are equally convenient to write. Outside of an IDE, option 2 wins, but option 3 isn't significantly worse. Since I'm German, I don't know how hard it is to type _:: on an American keyboard, but it can't be worse than typing SomeIdentifier::.

Readability

I'd argue that option 1 is the most readable since the types are visible; if we choose option 2 or 3, an IDE could provide type hints.

If types are repeated often in the patterns or are unimportant for understanding the code, less information can be better. With option 2 or 3, the programmer can choose when types should be explicit and when they don't need to be (as is the case with let bindings).

The main downside is that the programmer might be too lazy to specify the type explicitly, even if it would help readability. Therefore, some people might want a clippy lint that can be enabled to forbid inferred types in patterns.

One advantage of option 3 is that enum patterns can be easily distinguished from struct patterns and variable bindings.

Teachability

Option 1 wins here obviously.

Some programmers would find option 2 confusing, because the name of enums can be omitted in patterns, but not in expressions.

Option 3 has the same problem, but also suffers from the inconsistency that _ works for enums, but not for structs or unions — unless we want to also allow match .. { _ { .. } => () }. However, option 3 has the advantage that it's obvious where type inference takes place.

Diagnostics

I believe that option 2 would have a negative effect on diagnostics, because the same syntax, SomeIdent => .. can have different meanings. It can refer to an enum variant that is brought into scope automatically (unless type inference fails!). It can also refer to an enum variant or struct that is already in scope, or it can be a variable binding. So the compiler would have to do the following:

  • if a type with the name SomeIdent is in scope, use that type
  • otherwise, try to infer the type of the matched expression
    • if the type is an enum, bring its variants into scope
    • otherwise, assume that SomeIdent is a binding
    • if type inference for the expression failed, emit a type annotation needed error

Not only is this difficult to explain, it can also cause problems. For example, adding or removing an import can alter the program's behavior without causing a warning, and misspelling something might cause an incomprehensible compiler error message.

Compatibility

Option 2 is not backwards compatible, if people use lowercase enum variants or uppercase variable names, but the amount of breakage might be acceptable.

Syntax

The syntax of option 3 is more "Rust-y" than of option 2, because it can be easily explained in terms of type inference. The _ as a type placeholder is already used in other contexts, whereas option 2 is a completely new concept.

11 Likes