Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enum name elison through inferred variant constructors #1949

Closed
wants to merge 2 commits into from
Closed

Enum name elison through inferred variant constructors #1949

wants to merge 2 commits into from

Conversation

Rufflewind
Copy link

Rendered

Allow the name (qualifier) of an enum variant to be elided in expressions and patterns whenever it can be inferred.

(Previous forum discussion)


The feature can also provide a slight benefit for refactoring: if the name of
an enum is changed, code that uses inferred variant constructors would not
need to be renamed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it offers a disadvantage for refactoring as well, as you now can't grep over the codebase to find out where an enum is used when you e.g. want to rename a variant. Yes, you could just grep for the variant, but usually variants have very short and generic names (Local, Remote, etc.), so especially in larger codebases thats not possible.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would that not apply to imported variants as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phaylon when you import the variants, you'll still have to mention the name of the enum to do so. So grepping by the name of the enum will turn up that import location. And then after it you can refine your search, most often you'll just be able to ctrl+f as its all in the same file.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, now I understand. Personally, I see there being enough barriers to that already (import aliasing for one) that I'd hope that would be eased by cargo/RLS tooling at some point, since it is already a bit of a manual hunt. As such, I see the conveniences outweighing that for me.


The feature could encourage library writers to use domain-specific enums more
often, rather than to re-purpose generic enums like `Option` or `Result`,
leading to more readable code. It would also allow library writers to use
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option and Result have lots of functions on them, while custom enums don't, or first need them to be defined. They are very useful and allow for patterns like chaining.

And library authors can just declare a prelude with all of their enum variants inside. Or am I mistaken? idk...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The big plus I see is that it drastically reduces friction for example in the case when you want to have a descriptive enum instead of a boolean flag as function argument.

Copy link
Member

@est31 est31 Mar 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's an advantage. But then again, there is the builder pattern which works great as well (EDIT: so you won't need to use lots of booleans as flags to confuse the reader, but be able to make invocations like LogBuilder::new().channels(5).with_verbosity(true).create() instead of Log::new(5,true)).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option and Result have lots of functions on them, while custom enums don't, or first need them to be defined. They are very useful and allow for patterns like chaining.

They can be, but they are not always needed. The proposal is focused on reducing the boilerplate for “one-off” uses of enums, with a particular emphasis on users of multiple different libraries.

And library authors can just declare a prelude with all of their enum variants inside. Or am I mistaken? idk...

Preludes compound the risk of naming collisions between different libraries due to its naming pollution. They are also risky for library authors as any addition of items to a prelude can cause breakage downstream. It would also be difficult for preludes to handle multiple enums in which some of the variants have the same name.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you won't need to use lots of booleans as flags to confuse the reader, but be able to make invocations like LogBuilder::new().channels(5).with_verbosity(true).create() instead of Log::new(5,true)).

Actually a strong reason for this RFC is to replace ad hoc enums such as that. The ship has long sailed with regard to Rust’s context-sensitivity because of the .field syntax. This is what enables “builder” patterns such as what you describe.

So in essence what I am proposing is to simply add first-class support for what folks are already doing anyway, but at least they can do it without all the boilerplate needed for the builder pattern. They can also use a significantly more pleasant syntax:

Log {
    channels: 5,
    verbosity: _::Debug,
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ship has long sailed with regard to Rust’s context-sensitivity because of the .field syntax.

I'm a bit confused to what you refer to when you mean field syntax, as it doesnt make any sense. If you mean the ability to do .sth on expressions and then the sth field or sth function gets accessed, then it doesn't really serves your argument, in fact its the opposite. First, it doesn't really compare to enums, and if it did, it is an example for explicitness, not against it, as you always need something to apply it to. You can't say .new() out of the blue, you always need an expression for it. The equivalent to your proposal would be _.new() or something, but as I said before, it doesn't really compare.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, it doesn't really compare to enums

Fields are dual to inferred variants in the following sense:

  • The field syntax obj.sth requires the type of its argument obj to be known. You can’t write .sth without the argument obj.
  • The inferred variant receiver(_::Sth) requires the type of its receiving context to be known (equivalently, the argument type of receiver must be known). You can’t write _::Sth without its result being received by anything (e.g. { _::Sth; } is a type error).

variants of an enum without ever bringing the enum or its variants into scope.

This could lower the usability barrier for enums, particularly for library
users. It would reduce the verbosity of enums to a level similar to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if you use some enum once inside your entire codebase, then it'll reduce verbosity. But then again I think that verbosity is justified, to help readers find out what enum it is about. However, if you use an enum very often in your mod, you can import its variants at the top of the file, and use it throughtout the file. Then writing _::Alpha ten times is more verbose than writing use foolib::Foo::*; once and writing Alpha ten times (you spare 10 characters). So here its the opposite, the proposal will increase verbosity.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting to import all enum variants? That will get ugly as soon as there are variant name clashes.

Anyway, it definitely reduces the verbosity compared to the prefix::Variant case. And I think this is the one that is currently recommended by the style guide.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will get ugly as soon as there are variant name clashes.

When you have clashes, then its even more important to distinguish between them inside your code. Otherwise you lose readability.

And I think this is the one that is currently recommended by the style guide.

This is an RFC to propose changes to the rust language, not the official rust style. If you don't like the official style, propose to change it instead.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you have clashes, then its even more important to distinguish between them inside your code. Otherwise you lose readability.

I believe that in cases like function arguments the context provides readability. In general I agree with you though. I'm in the "import as little as possible" camp.

This is an RFC to propose changes to the rust language, not the official rust style. If you don't like the official style, propose to change it instead.

Change it to what? I see disadvantages in both "prefix all the variants" and "import the variants" styles. I feel like a solution with the functionality like this RFC is a third, in between way that hits a sweet spot.

@burdges
Copy link

burdges commented Mar 11, 2017

I have not decided if I like this RFC or not. It's fine to write use MyBigLongEnum as E;, which solves this more explicitly. I think that's even idiomatic Haskell.

That said, an argument in favor of this RFC is that use Foo::* brings new into scope if you wrote :

enum Foo { .. }
impl Foo {
    pub fn new() -> Foo { .. }
}

I could imagine use Foo::enum; or similar might bring only the variants into scope though.

Another point that might support or oppose this RFC is that custom enums intentionally overlap sometimes, like when they represent different states at different steps in some process. I recently wrote something like :

enum Command {
    ...
    Transmit { .. },
    Deliver { .. },
}
enum Action {
    ...
    Transmit { .. },
    Deliver { .. },
}

In general, I could imagine some syntax like use infer Foo::{new,Variant}; that said "bring these into scope method-style", meaning if you can write new(..) or Variant(..) and it can infer Foo then it should do so. Replace infer with maybe type if you want to avoid keywords.

An an aside, can you use wildcards inside names, like use Foo::new_*;?

@killercup
Copy link
Member

killercup commented Mar 11, 2017

Random thoughts, assuming enum Foo {Bar, Baz}:

  • I don't really care about this in match, as the enum we are matching against is more obvious there than in function calls with enum parameters, and it's easily with it to write the use if we match ≥3 match arms.
  • _::Bar is ugly IMHO
    • but we probably can't use .Bar as shortcut syntax (I think Swift does this)
    • and using Bar by itself and without use Foo::* is too magical
  • Is there an editor plugin (RLS?) that can automatically insert the function signature with the enum path?
    E.g., for a fn foo(bar: Foo) {} it autocompletes foo<Tab> to foo(Foo::) (unless Foo::* is already in scope).


Inferred variant constructors do not take the namespace into consideration at
all, nor do they affect the namespace. It is purely type-directed, albeit
subject to visibility rules.
Copy link
Contributor

@Ixrec Ixrec Mar 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need more detail on what these last six sentences mean (i.e., the actual rules of how this feature works). They sound precise, but when I asked myself "is this RFC introducing a (new?) form of ADL?" and tried to find the answer here, I honestly couldn't tell if this wording is deliberately introducing ADL or deliberately avoiding it, though it sounds like it's trying to do one of those things.

To save non-C++ people from reading that link: it's not at all clear to me whether this RFC would require a use Foo; or use Foo::*; statement in the same file or the same module or not at all in order for _::FooVariant to type check.

Simply adding compiles/doesn't compile code samples under each of these statements would help tremendously.

(personally I'm not a fan of this RFC because I just don't see the advantage of the _:: operator over a simple use MyEnum::*; statement, but in the process of trying to articulate why I realized I don't even understand what the proposed behavior of _:: is)

Copy link
Author

@Rufflewind Rufflewind Mar 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ADL for several reasons:

  • One is that it never takes namespace information into account, only type information.
  • The other is that it considers the type of the receiving context, not the type of the argument.

I’ll add examples to clarify the behavior.

I just don't see the advantage of the _:: operator over a simple use MyEnum::*; statement

use MyEnum::* pollutes the local namespace, whereas _:: does not. It also requires adding a use anywhere between a few lines above or at the top of the module, whereas _:: maintains locality.

Edit: Submitted too early.

@Rufflewind
Copy link
Author

@burdges

I have not decided if I like this RFC or not. It's fine to write use MyBigLongEnum as E;, which solves this more explicitly.

It’s works, but I see that as more of a workaround for a missing feature than a proper solution. It would require inventing a letter for it even though the type system knows very well what the enum should be. The point of this RFC is to reduce the amount of use clutter necessary to use enums.

I think that's even idiomatic Haskell.

Haskell’s ergonomics with sum types (and records) is quite poor IMO. In Haskell sum types bleed into the surrounding scope so one has to either define them in their own separate files and then import them qualified or use prefixes like EAdd or EMultiply.

Rust, unlike Haskell, has been historically quite bold with additions to its type inference, even if it meant the user would have to add type annotations more often. The obj.field syntax is a perfect an example of this, where inferability was sacrificed for convenience. This proposal is basically an extension of the obj.field idea, but as applied to enums.

That said, an argument in favor of this RFC is that use Foo::* brings new into scope if you wrote :

I don’t believe Rust does that.

In general, I could imagine some syntax like use infer Foo::{new,Variant}; that said "bring these into scope method-style".

The use kind of defeats the purpose, since one of the goals is to reduce the number of boilerplate use statements.

@killercup

  • and using Bar by itself and without use Foo::* is too magical

It could also break existing (albeit non-idiomatic) code that use Bar as a variable name inside a match clause. I think a sigil of some sort is mandatory here to make the elison obvious to the reader.

@burdges
Copy link

burdges commented Mar 11, 2017

I see. You cannot bring Foo::new into scope as new even if you're explicit about it because Foo is not a module.

@leoyvens
Copy link

but we probably can't use .Bar as shortcut syntax (I think Swift does this)

@killercup Why not? Seems like a sweet alternative.

@Ixrec
Copy link
Contributor

Ixrec commented Mar 11, 2017

.Bar feels too confusing for me. It makes Bar look like a field instead of a variant.

Though I am less opposed to ::Bar than I am to _::Bar.

@comex
Copy link

comex commented Mar 11, 2017

What about :Bar?

@scottmcm
Copy link
Member

I came in here from IRLO thinking that _::Bar was a great extension of _ meaning "please infer this" in types, but the comments convinced me otherwise. Unfortunately, it makes it look like "path component inference", and I'd worry that people would expect std::_::_::V4(addr) and _::default() to work. (I saw the latter suggested recently, but don't remember where.)

What about using enum somehow? It's currently always followed by an ident, AFAICT, so something like enum::Alpha("hi") would work.

(I'd be tempted to say just enum Alpha("hi"), but enum Beta { gamma: true, delta: '~' } takes a while before even a human can tell what it's going to be.)

Analogous future work: If this uses enum because _ is disliked, then inferred-type struct literals could similarly use struct (instead of _ mentioned in the thread), like dot_product(struct { x: 1, y: 2 }, struct { x: 7, y: 8 }).

@est31
Copy link
Member

est31 commented Mar 12, 2017

@comex that will get in the way of type ascription.

@est31
Copy link
Member

est31 commented Mar 12, 2017

and ::Bar is taken already as well, its for bar in the root namespace.

@nrc
Copy link
Member

nrc commented Mar 12, 2017

I find it useful having the full name of an enum, in particular it allows for a nice pattern of naming without prefixing, e.g.,

enum SupportFoo {
    Yes,
    No,
}

Now wherever I use this, my code is self-documenting. If the SupportFoo bit is elided, then I need to change the enum to

enum SupportFoo {
    SupportsFoo,
    DoesNotSupportFoo,
}

So I end actually having to type almost as much with elision as without and, the enum decl gets uglier.

@comex
Copy link

comex commented Mar 13, 2017

@est31 I don't think it would interfere with type ascription, as type ascription colons appear in the 'after expression, expecting binary operator' state, while this colon would appear in the 'expecting expression' state. (Rust's grammar already has constructs that parse differently depending on this state: < at the start of an expression is assumed to be an associated item projection, and makes the parser start treating <> as delimiters, whereas < after an expression is less-than and keeps the parser in expression mode.)

@joshtriplett
Copy link
Member

Wouldn't this make more things into breaking changes that previously weren't?

Suppose I write _::Foo(..) to refer to A::Foo. That will stop compiling if any other enum in scope adds a Foo variant. So, if I already imported B, and used an individual item from it like B::SomethingElse, but B then adds a B::Foo, the reference to _::Foo(..) will become ambiguous.

@est31
Copy link
Member

est31 commented Mar 15, 2017

@joshtriplett I think the idea is that this elision only works when the needed type can be inferred from what the result is supposed to be. So let f :Foo = _::Blah; if Foo contains variants One and Two, and enum Bar contains variants Blah and Three, will give you a "variant not found" error instead of a type mismatch.

@Rufflewind
Copy link
Author

@joshtriplett To add to what @est31 has said, the proposed inference mechanism does not take into consideration what exists or doesn't exist in the current scope.

@withoutboats
Copy link
Contributor

@nrc I don't think this code is particularly unclear:

match self.supports_foo {
    _::Yes => { ... }
    _::No => { ... }
}

If your variable is named well, we'll know what the elided type is. If not, chose not to elide the type.

That said, I'm not convinced this is better than just use self::SupportsFoo::*.

@burdges
Copy link

burdges commented Mar 15, 2017

Just backing up for a moment : You could use inference to give a warning instead of an error on say let supports_foo = Yes;, match supports_foo { Yes => .., etc. In principle, the warning could be smarter, like not complaining when an explicit type ascription, function argument, etc. fixed the type, which sounds like this RFC minus the _:: sigil. You've indicated the RFC needs the _:: sigil since #![allow(non_snake_case)] allows a local variable Alpha to conflict with your variant Alpha. If that's were only worry, then #![allow(non_snake_case)] could make this warning more aggressive, although maybe that's too many moving parts. In fact, it seems worse than that though, as types could conflict with your variant in any code. In other words, it's variants conflicting with types that requires the sigil, yes?

As an aside, I've kinda wanted to place a use statement inside a match statement before, like

match self.supports_foo {
    use self::SupportsFoo::*;
    Yes => { ... }
    No => { ... }
}

@Rufflewind
Copy link
Author

@burdges

Those are the reasons. In general it would be best to avoid sensitivity to the availability of identifiers in the current scope. The proposal is intended to be analogous to .field in that _::Variant exists within a separate namespace entirely.

@Rufflewind
Copy link
Author

Here are the proposed (unambiguous) syntaxes so far:

Let me know if I missed someone or any pros/cons.


This was mentioned in the RFC a few times, but it might be worth elaborating on the commonalities between inferred variants and field expressions. Since I'm not committed to any particular syntax, I'll use to designate the sigil here.

Suppose Rust didn't have field expressions:

  • To access a field name from a struct Person, one had to write name(person) while also doing use some::long::path::Person::name.
  • If two types Person and City both have the same field name, they had to be qualified as Person::name(person) or City::name(city).
  • Alternatively, one could use …::Person as P and use …::City as C to make things shorter, but now the type namespace is polluted with P and C.

Now consider the Rust we have now:

  • (+) To access a field one can simply write person.name.
  • (+) There is no naming collision, because fields live in their own namespace.
  • (+) There is no need to use some::long::path::Person except to mention the type (or construct it⁺).
  • (-) Readability can be worse, especially if it's hard to tell what the LHS is.
  • (-) Refactoring can be difficult because it's unclear where all the use sites are.
  • (-) Inference is harder because type of the LHS must be known.
  • (-) (Unique) Readability and inference is further complicated by overlap with trait syntax and autoborrowing.

What about the current situation without inferred variants?

  • To create a variant Text for an enum Xml, one has to write Text(…) with use some::long::path::Xml::Text;
  • If two types Xml and Html both have the same variant name, they would have to be qualified as Xml::Text(…) and Html::Text(…).
  • Alternatively, one could use …::Xml as X and use …::Html as H to make things shorter, but now the type namespace is polluted with X and H.

With the proposal:

  • (+) To create a variant one can simply write □Text(…).
  • (+) There is no naming collision, because inferred variants live in their own namespace.
  • (+) There is never a need to use some::long::path::Xml except to mention the type.
  • (-) Readability can be worse, especially if it's hard to tell what the receiver expects.
  • (-) Refactoring can be difficult because it's unclear where all the use sites are.
  • (-) Inference is harder because type of the receiver must be known.
  • (-) (Unique) It's slightly longer to write □Text(…) than Text(…). This is unavoidable because there has to be some way to indicate that the token lives in a separate namespace.

It has also been argued that field expressions alone are sufficient to emulate inferred variants through the builder pattern. But:

  • Builder patterns are verbose and require boilerplate.
  • The flow of type information is different: during inference, the builder pattern still expects the LHS of . (input) to be known, whereas inferred variants expect the receiving (output) type to be known.

Therefore, inferred variants are an orthogonal feature from an inference standpoint.

Are the advantages of this feature significant? IMO, not any more than field expressions, if one sets aside the fact that Rust was very much built around field expressions. Naturally a lot of Rust's features are entangled with it, but it would have been entirely possible to design the language without field expressions. After all, Haskell is a perfectly productive language even without the OverloadedRecordFields extension. In Haskell, one simply has to qualify the field names from time to time and be willing to use (i.e. import) things more frequently.

⁺ Removal of "or construct it" is a candidate for another proposal.

@clarfonthey
Copy link

If these matches are done in methods of an enum, you can always use Self.

I personally do use self::Enum::*; inside methods a lot and generally prefer that.

@Boscop
Copy link

Boscop commented Mar 15, 2017

I agree with @nrc, I prefer to write the explicit/long enum name, but I also agree with @burdges:

I could imagine use Foo::enum; or similar might bring only the variants into scope though.

For those cases when all enum variants should be imported into a scope but not Foo::new() etc.

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label Mar 15, 2017
@burdges
Copy link

burdges commented Mar 15, 2017

Could I write beta::_::Yes, alpha::_::No, etc. if I had the following?

mod alpha {
    enum Mine {
        Yes, No,
    }
}

mod beta {
    enum Mine {
        Yes, No,
    }
}

@DorianListens
Copy link

Speaking as both a swift and rust user, the .variant syntax in swift is one of its nicest features, and one that I miss the most when working with rust. In my experience, it does not make things less readable or self documenting, it is easy to type, and makes working with swift enums ergonomic and enjoyable.

@joshtriplett
Copy link
Member

@burdges I was trying to suggest a more concise alternative. But yes.

@solson
Copy link
Member

solson commented Mar 25, 2017

Inner enum problem

While thinking up an alternative proposal I ran into a problem and I realized @joshtriplett's idea has it, too. It's not satisfying to only pull in variants of the outermost enum, because I often want to omit enum name prefixes in subpatterns as well, like so:

use path::to::OuterEnum::*;
use path::to::InnerEnum::*;
match e {
    OuterVariant(InnerVariant) => ...

So I would only support a proposal that solves this inner enum problem.

use problems

Now, I'm already pretty happy with these uses, but I see two problems:

  1. Even if Enum is already in scope, you still need use full::path::to::Enum::*; or use self::Enum::*; which is a lot of noise!
  2. The enum variants pollute the whole scope, rather than only being in scope for the match patterns.

@burdges's suggestion solves problem 2:

match e {
    use path::to::OuterEnum::*;
    use path::to::InnerEnum::*;
    OuterVariant(InnerVariant) => ...

But I think it's too small of a win to justify by itself. I'd prefer to solve problem 1 at the same time.

Proposal

So I would have the user mention OuterEnum and InnerEnum by local relative paths without any of that use path::to:: ::*; syntax noise, and I would tie the syntax to the match so it's clear the variants are only in scope for the patterns.

The problem is I'm not sure what the best look for this syntax would be. I'd like it to be as concise as this:

match e {
    use OuterEnum, InnerEnum;
    OuterVariant(InnerVariant) => ...

But people might have misgivings about using use in a new way, with non-absolute paths. Does anyone else like this? Can someone think of a better syntax for this idea?

Compared to the original RFC

Referring to the enum names explicitly makes this an easy to implement proposal with no inference problems. This syntax is wordier than the original RFC, but it makes the code self-documenting and it's an improvement over the use glob status quo. I think my syntax would be more googleable, via the plausible search term "Rust match use".

@solson
Copy link
Member

solson commented Mar 25, 2017

I think we could also parse this syntax, saving a vertical line:

match foobar use OuterEnum, InnerEnum {
    OuterVariant(InnerVariant) => ...
match rfc.error use RfcError {
    SyntaxNotGoodEnough => self.generate_syntax_idea(&mut rfc),

@withoutboats
Copy link
Contributor

Rather than coming up with new syntax for this we could just only do this sort of name resolution if the expression is type ascribed:

match x: SupportsFoo {
    Yes => { }
    No => { }
}

All of these ideas seem like a layering violation, but at least this isn't any new features.

@solson
Copy link
Member

solson commented Mar 25, 2017

@withoutboats I don't think that's good enough (inner enum problem again).

@solson
Copy link
Member

solson commented Mar 25, 2017

That said, I wanted to get my idea in front of some other people, but I'm also pretty comfortable just doing nothing about this.

@withoutboats
Copy link
Contributor

@solson since the type is fully concrete, you can see through inner enums (though they won't be named locally). That is, you know that Foo::Bar contains a Baz type, so you can just do Baz(Quux).

@solson
Copy link
Member

solson commented Mar 25, 2017

@withoutboats Oh, I didn't realize it was going to implicitly pull in all nested enums... hmm. Is that too surprising?

@withoutboats
Copy link
Contributor

@solson It could or it couldn't. I don't think I'm in favor of any of this vs just writing normal use statements. I agree that its annoying that the use is absolute, but that's a more general problem.

@clarfonthey
Copy link

For the use syntax, I think that something along the lines of use Enum in match val { ... } might be best.

@burdges
Copy link

burdges commented Mar 25, 2017

If the type ascription approach does not cause problems elsewhere, then one should consider how far that can be pushed outside of match because folks might want it everywhere. Should Let supports_foo : SupprtsFoo = Yes; or Yes : SupprtsFoo or .map(|x| { .. }).collect() : Vec<..> work? etc.

I donno if in is a keyword in Rust but use .. in .. but postfix use or using, ala match .. { .. } use ..; provide a similar feel. In both cases, these syntaxes make sense for arbitrary expressions, so again one must consider how far they should generalize and whether variants should be pulled in or not.

I think the match .. use .. { .. syntax indicates a restriction, like this should not generalize beyond match or that variants should be pulled in but only for match.

As an aside, I suppose the let x = foo in bar syntax from Haskell does not buy anything over { let x = foo; bar }.

@petrochenkov
Copy link
Contributor

petrochenkov commented Mar 25, 2017

This @solson's solution - #1949 (comment) - is probably the least invasive solution I can imagine.

I still disagree with the motivation though because I personally use (new style) variants in qualified form only Enum::Variant, and I'm also quite annoyed when someone "cleans up" these Enum::Variants with local use Enum::*;.
It's immensely useful for code searching/investigating and refactoring because Variants often have some generic name like Yes, or Error, or other name common in the codebase.
While use Enum complicates things, it is at least searchable itself, the problem will become worse if use Enum becomes implicit and non-searchable.

@joshtriplett
Copy link
Member

Comments #1949 (comment) and #1949 (comment) still reflect the opinions of the language team towards this problem. On that basis:

@rfcbot close

@nrc
Copy link
Member

nrc commented Apr 6, 2017

@rfcbot fcp close

@rfcbot
Copy link
Collaborator

rfcbot commented Apr 6, 2017

Team member @nrc has proposed to close this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot
Copy link
Collaborator

rfcbot commented Apr 27, 2017

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot added the final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. label Apr 27, 2017
@rkjnsn
Copy link
Contributor

rkjnsn commented May 3, 2017

This is something I've wanted, and I agree that _::Variant seems the most obvious, as _ is being used as "infer this type" as elsewhere, though I wouldn't be opposed to another such as .Variant. I definitely feel this comes up enough to warrant it's implementation, and workarounds such as use path::to::MyEnum as M feel very kludgy.

may affect naming of variants (as @nrc wrote)

I don't think this is the case. As @withoutboats points out, in many cases it will be clear from the context, anyway, and one can always choose to include the enum name if it makes the code clearer.

@rfcbot
Copy link
Collaborator

rfcbot commented May 7, 2017

The final comment period is now complete.

@aturon
Copy link
Member

aturon commented May 8, 2017

Closing as per FCP. To be clear, there is a genuine ergonomics issue here, and it's a problem that is certainly within the purview of this year's productivity roadmap, but the design here doesn't quite hit the sweet spot.

If you have more ideas on this topic, or want to follow up on one of the linked comments with a pre-RFC, please feel free to open an internals thread on it, or reach out to me (or others) on the lang team for mentoring.

@aturon aturon closed this May 8, 2017
@Rufflewind
Copy link
Author

Thank you all for reviewing this!

@photino
Copy link

photino commented Jan 25, 2021

Since enum and module path both use ::, we can consider the match ... in ... {} syntax. For example, the following code

match res.status() {
     tide::StatusCode::NotFound => Ok(tide::Response::new(tide::StatusCode::NotFound)),
     tide::StatusCode::InternalServerError => Ok("Something went wrong".into()),
     _ => Ok(res),
}

can be written as

match res.status() in tide::StatusCode {
     NotFound => Ok(tide::Response::new(NotFound)),
     InternalServerError => Ok("Something went wrong".into()),
     _ => Ok(res),
}

It can also be used for matching any statics in a module path:

match value in mycrate::mymod {
     ONE => 1,
     TWO => 2,
     _ => 3,
}

if we have defined ONE and TWO in mycrate::mymod:

pub static ONE: usize = 0;
pub static TWO: usize = 1;

@ghost
Copy link

ghost commented Feb 4, 2022

Sad that this is not in Rust yet. One of the best features in Swift and would love to see it in Rust.

@ghost
Copy link

ghost commented Feb 4, 2022

As far as I can see, the problem basically lies in possible confusion/overlap with module syntax. Makes me wonder... why was :: chosen to access enum variants in the first place, instead of . field syntax?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.