-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: Using enums like traits #2618
Conversation
This should probably be in the summary. This RFC seems similar to #1450. |
text/0000-impl-enum.md
Outdated
|
||
This new approach will not create enum types just to match them directly afterwards, when it's still known, and removes a little unnecessary overhead. | ||
|
||
But this comes with problems. When adding a new event type, it has to be added in many places instead of just once in the enum declaration and once in the match. The match even will care if you forget the branch. So this small performance benefit is most likely not worth it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"The match even will care if you forget the branch" what does this mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
match
in rust generates a warning, when the match is not exhausting, so when adding a new variant to the enum, you won't forget to add this branch in match
. That's nothing new, but an advantage opposed to the more efficient approach I mentioned.
Maybe I should reformulate it this.
|
||
```rust | ||
trait Trait { | ||
fn test<T: Enum>(&self, arg: Enum); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be arg: T
? Doesn't make sense otherwise.
Enum types can just be used like trait types now: | ||
|
||
* It's preferred to add a `dyn` modifier, when using an enum directly. | ||
* It's also possible to use enums for generics additional to traits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this exactly mean?
This basically seems like using enums like sealed traits. All of the prior art regarding sealed traits, and sealed traits as an alternative, should probably be included in the RFC. Alongside the prior art from Kotlin, which allows sealed classes natively in the language. |
I think this is a nice concept, but some things are still unclear to me, and I wish the reference-level explanation were fleshed out. I can see that you think the guide-level explanation is clear enough, but I can't agree; some things are still a bit unclear to me, such as how matching works, what the deal is with using enums as associated types, etc.
Also, if I understand it correctly, and you want to be able add associated enums to traits, I do not believe such a feature should be in this RFC, but instead in another RFC. In addition, I don't believe associated enums is a feature that should even be added, since it doesn't make much sense; it would be much better if traits could have associated traits (and if traits could also be generic arguments, like lifetimes and types and now constants too, but that may also be another RFC), since associated traits would also work with enums with this proposal. |
I have to look at this.
I also thought first, this should be included in a different RFC, but I couldn't get my example working in a generic way without these. Maybe they should be added to |
@porky11 please update the rendered link, preferably to a branch |
This seems to be a massive change. I'm really concerned about backwards compatibility, especially in the light of the sentence "This would ensure backwards compability" in the Alternatives section. This also severely increases the complexity of the language, which is even more worrying. In the example, you write:
In terms of what kind of resource is it more efficient? Execution time? Sure, the event types are known at compile time, but events can still be dispatched in any order at any time, can't they? Do you mean that
Why would you create an enum if not for matching on it? The main point of the enum as a language construct is to express alternatives, and the way to extract knowledge about those alternatives is pattern matching against variants. Deeming this useless doesn't sound like a considerate argument to me. As to the feature itself, I have several conceptual problems with it. It tries to treat two very different constructs (
Considering the matching example: if let Struct2(...) = Struct1 {
...
}
while let Struct2(...) = Struct1 {
...
}
I don't get what exactly is meant by this. Is this code at the top level? Within a function? How can a type of
I'm sorry but I don't understand this at all. What is required to be an enum? What are we making into trait objects? The enum itself? Or something else? What is a "dynamic enum", and in what way does it differ from regular enums? What does it mean to "use multiple dynamic enums for traits", and why should it be avoided?
What does this do, what kind of behavior does it have, and why is it useful? To sum up, this RFC would introduce a high amount of complexity and non-intuitive behavior, bringing with itself elements that look and work very differently from what long-time users of the language are accustomed to. It is also (apparently) a breaking change, and quite a big break in fact, because it affects two of the most fundamental parts of the type system, traits and generics, in many ways, as well as the creation and behavior of Unfortunately, the explanations are often unclear and missing important details as well. This makes it even harder to appreciate the motivation. Overall, in the light of this I don't think the benefits weigh up to these significant downsides at all. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My overall feeling is that this RFC needs several times more detail than it currently has, especially wrt. the reference. I also think that #2593 is a more limited proposal that seems to achieve some of the goals set out but with less complexity.
|
||
Enum types can just be used like trait types now: | ||
|
||
* It's preferred to add a `dyn` modifier, when using an enum directly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear what this entails from the phrasing.
// update implementation to match new version of Update | ||
enum Event = Event; | ||
fn update<E: Event>(&self, event: E) -> bool { | ||
match event { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that some sort of type-case is going on here... could you elaborate?
If I didn't know that Event
is a bound here I would interpret this as dependent typing...
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
It seems the explanation in the previous part is already detailed enough for most parts and moving parts down here will just make it less clear. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not at all. It's not even clear at a high level from the guide what this does let alone what impact it would have on Rust's static semantics, dynamic semantics, and syntax.
text/0000-impl-enum.md
Outdated
let value: EnumName::Variant1 = EnumName::Variant1; | ||
``` | ||
|
||
But here it's confusing, why this version is selected, so I decided against. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elaborate, why is it messy?
Yes, these are true: It's a massive change and increases the complexity. But backwards compatibility should not be a problem. Everything, that currently works, would just work slightly different internally and probably more efficient. Changes to already working code are not needed.
The enums will need less memory. In case, the enum variants differ in size very much, this may even be recognizable in some cases. It will also be more efficient, because it's not necessary to match at runtime anymore.
It's intended to match on the enum. But since it's already known at compile time, which enum variant is chosen, it's possible to select the branch at compile time, too.
There is no difference between variant structs and normal structs.
Enums and traits in this proposal work similar. That's why I chose to use the same syntax.
In this case, when both structs are enum variants, the valid branch would never match, so it's never called. This can be ensured at compile time here, so this branch can be removed.
So no, the pattern of one can't be bound to the other. Only if they implement the same enum, the match will not be an error, but instead just be discarded.
A dynamic enum is the current version of an enum.
This is necessary to get my example work in a generic way. I could not find a different way.
Yeah, the complexity is also the main drawback for this. But I think, this is a pretty intuitive behavour and also shouldn't break anything.
I try to make it clearer. I hope, this already helped. |
There's already a working Clippy lint for that. It also comes with the suggested solution: boxing the large variant(s). It doesn't need to be any more elaborate than that.
I'm not sure what you mean by "you want every function to be optimized at compile time". Surely the compiler optimizes every function at compile time (if you so request, e.g. using the Later you assert:
So basically this is not true, and even if it were, a simple, local, well-understood Overall it seems to me that you are heavily underestimating the standard optimization capabilities of modern compilers (including rustc and its backend, LLVM). I suspect that the vast majority of the improvements you assign to this RFC are already realized by existing optimization strategies in the compiler. This makes the perceived gains marginal compared to the increase in language complexity.
The very RFC text says that this is not true:
They might under this proposal, that's exactly my complaint. Currently they don't, and that is the right thing to do, but which the RFC wants to change. One could probably bend any two unrelated language features to work similarly, but that's not a feat unto itself. There's little point in merely duplicating features, and forcing the same behavior upon them even if they had been initially designed to solve different problems is actively harmful.
What's the difference between dynamic enums and today's (regular) enums then?
What kind of "behavior", precisely? Trait methods? Enum variants? Doesn't the former just have the same effect as a trait object, and the latter that of the existing enum type?
I do believe that, it's just that I missed a more detailed (possibly technical) explanation as to why that is the case. |
@rfcbot fcp close In general, I do think that enums and traits have interesting overlap -- there is a clear desire to have types for enum variants (as in #2593) and it seems like enums and sealed traits are conceptually very similar. Despite that, I am moving to close this RFC, for a few reasons:
|
Team member @nikomatsakis has proposed to close this. The next step is review by the rest of the tagged teams:
No concerns currently listed. Once a majority of reviewers approve (and none object), 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. |
This would then be possible without the need of boxing values. But that's not a large benefit. I just wanted to mention it.
If the compiler is already capable of creating specialized versions of every function, that uses a variant, known at compile time, this RFC really isn't needed. But I don't think, it is. This would need a lot of rust specific knowledge, which LLVM doesn't have anymore.
If structs are defined to implement a trait, they just have additional capabilities. They can be matched as normal and additionally with other structs, that implement the same trait, which will just remove the match arm at compile time.
As said, they solve kind of the same problem: Generating specialized versions of functions at compile time.
There is no difference at all.
"behaviour" is, what the funciton does. Not sure, what you want to know here exactly.
It's needed for some type to implement some Enum, but the Enum to be required to be implemented may be different.
This way it would be allowed to define multiple traits, where the method accepts types with multiple different trait requirements. This is not really useful yet, since you would rather add the generic type directly to the trait. But if it was possible to ensure, that there is a fixed set of types, that can implement requirement, it's useful to set the Requirement, so it's possible to generate trait objects for traits with generic functions. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period, with a disposition to close, as per the review above, is now complete. By the power vested in me by Rust, I hereby close this RFC. |
Rendered