-
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
Allow arbitrary enums to have explicit discriminants #2363
Conversation
This introduces one more knob to the representation of enumerations. | ||
|
||
# Rationale and alternatives | ||
[alternatives]: #alternatives |
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.
I think you have encountered the XY Problem. You are finding a solution to the problem that different enums don't give guarantees about their variant indices, when what you originally wanted was converting enums whose variants have fields into enums whose variants have no fields.
So here's my alternative:
Allow annotating enums with #[discriminant(OtherEnum)]
, where OtherEnum
must have a variant for each variant in the annotated enum (with matching name). The annotated enum will then take all its discriminant values from OtherEnum
.
as
casts could then convert from annotated enums to their discriminant 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.
That seems more convoluted, IMO. Definitely harder to implement, at least.
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.
Sure, but it has a path forward for having this operation in safe code.
Alternatively a procedural macro could generate said conversions with this RFC. So the compiler magic might not be necessary.
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.
This would be great and nice, if only real world allowed me to do that.
I simplified my use case for the sake of the RFC: PropertyDeclaration
actually has more variants than LonghandId
, thanks to custom CSS properties (and some other stuff that I don't need to mention here):
#[repr(u16)]
enum PropertyDeclaration {
Color(Color),
Height(Length),
InlineSize(Length),
TransformOrigin(TransformOrigin),
Custom(CustomDeclaration),
}
struct CustomDeclaration {
name: Name,
value: Value,
}
pub enum PropertyDeclarationId<'a> {
Longhand(LonghandId),
Custom(&'a Name),
}
impl PropertyDeclaration {
pub fn id(&self) -> PropertyDeclarationId {
if let PropertyDeclaration::Custom(ref declaration) = *self {
return PropertyDeclarationId::Custom(&declaration.name);
}
let id = unsafe { *(self as *const _ as *const LonghandId) };
PropertyDeclarationId::Longhand(id)
}
}
In general, I think this alternative is too specific to the exact use case described in the RFC, and cannot fulfil more intricate ones like the actual stuff I require in Servo, or use cases I could imagine for this feature with FFI, @gankro may have an opinion on that regard here.
Edit: I'll edit this RFC to include ite nonetheless.
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.
You mention in the RFC that rust is generating a 4KB jump table for the required match expression - this seems excessive, even if the discriminants don't match exactly. How many variants does this enum have? Maybe part of the solution should be improving the code that rust generates in this case.
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.
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'd be cool if this worked with ATCs to permit defining both discriminant and enum together:
We've a circular definition above, but rustc could presumably break this cycle. |
@burdges That seems unrelated to the RFC described in this pull request. |
I can totally sympathize with this RFC. I have often wanted to go from an enum with fields to a value of another type. My biggest two gripes against this RFC are
In that light I propose the following variant/alternative: // same as before
#[derive(Clone, Copy)]
#[repr(u16)]
enum LonghandId {
Color,
Height,
InlineSize,
TransformOrigin,
}
// explicitly mention that the given type is the descriminant (it can also be a struct!),
// but the compiler needs to be able to prove at compile time that two values of the
// descriminant type are actually different. Note: different means different bit patterns,
// not different in the `Eq` sense.
#[repr(LonghandId)]
enum AnimationValue {
Color(Color) = LonghandId::Color, // no cast
Height(Length) = LonghandId::Height, // also each descriminant assignment must be unique!
InlineSize(Void) = LonghandId::InlineSize,
TransformOrigin(TransformOrigin) = LonghandId::TransformOrigin,
}
impl AnimationValue {
fn id(&self) -> LonghandId {
(*self) as LonghandId // compiler can just return the descriminant! Completely safe!
}
} |
We could support discriminants with their own fields too, making this these enums into extensions. I suppose |
Please, I have already addressed that this doesn't fit my use case in #2363 (comment) and the RFC already includes such an encoding as an alternative, except with Removing the need for unsafe code for the cast is a nice thought, but again it is unrelated to this RFC. |
@nox Sorry if this is a stupid question, but I still don't understand how the two proposals are not functionally equivalent. It seems like everything you can do in one proposal, you can do in the other. What am I missing? |
I assume this is restricted to enums with a non-Rust |
@RalfJung I had the same reaction. nox and eddyb explained that this RFC controls the discriminant of each variant, but this is a separate concept from the tag that may or may not be part of the memory representation. But yes, since |
@mark-i-m #2363 (comment) One of the enums with fields has more variants than |
@SimonSapin I am not sure I understand, but if the feature is not useful for |
@RalfJung I listed it in the unresolved questions. I could imagine rustc managing to use niche-filling optimisations for 2 different variants with an inner enum, if it could see that they use disjoint sets of discriminants and that there is an easy way to compute the discriminant from the niche value. This is quite theoretical though so I don't mind mandating a |
I realy like the With
Also, There are two problems left for my taste:
Now, about the first point... I especially like the #[repr(u8)]
enum X {
A(u32) = 0,
B(f32) = 1,
}
assert!(X::A(42).id() == X::A);
assert!(0_u8 == X::A); This could, however, mean trouble if So... how do we get that discriminant? Writing a pointer-converting boiler plate function called I'm not really up to date on the An alternative would be to treat enums logically as a Edit: Just noticed this:
Under the default representation, there might be heavy layout optimisations at work merging discriminants of nested enums into a completely different numeric set. In addition to that |
@Evrey I'll add a commit listing all your arguments against How does one name those explicit discriminants?We can't interpret How does one get those discriminants?I agree that a way to safely get the |
I see... but I still have the same two annoyances against the current form:
Especially the second one seems like an essential part of any reasonable solution IMHO... |
Given what I said about |
Note that this is #1872. Feels like a quite elegant workaround to me - the unsafe cast asserts that the |
@main-- I know about this RFC, but it has been postponed and I don't think I should have to rely on that instead of just not having |
@nox I mean, you're essentially trying to come up with a way to define Rust enums with gaps in their discriminator space - this RFC proposes to explicitly specify the remaining ones whereas I suggested to list the missing ones. It's fundamentally the same thing, depending on the usecase either one may be more convenient. Yet I favor the |
Having to specifically define variants that are uninhabited for no other reason than to make 2 discriminants in 2 different enums coincide is a damn workaround that I will not qualify as "fundamentally the same thing" as being able to omit said variants. Why would I pollute my mind and my type definition with variants that I won't use? |
I guess if most of your variants are dead it makes considerably less sense than, say, one of thirty. The thing is - comparing a language feature tailored specifically to this problem to a general mechanism that the language already offers (minus a papercut) just isn't fair, hence my reluctance to call it a workaround. My point is that the alternative is not simply omitting the dead variants. Instead, you have to manually specify discriminants. When I say it's the same thing what I mean is that we're talking about two different ways to specify enum discriminants (same outcome). Your proposal introduces a new language feature which is clearly an improvement over the (mostly) existing workaround, I merely doubt that it's enough of an improvement to justify the complexity. |
This is a very small addition to the language, semantics-wise and implementation-wise. As far as I remember discussions with @eddyb, this will be easily implementable. |
I don’t believe added complexity is significant here, the same syntax already exists on field-less variants. |
I went back and re-read the RFC and I think my objections are more specific about the example. The actual proposal is just about allowing |
(Aside: there’s a bunch of things in Stylo that are objectionable ;) (I say this as the author of many of these things.) A lot of it can be improved but the devil is in the corner cases, and better solutions are often not as easy as they first seem.) |
I may be not reading correctly, but does this work for
have size zero or one? Also, would:
have size 0 or 2? (These are super esoteric examples but it seems like a valid thing to ask considering how imho these don't make sense, but would be accepted.) |
@pnkfelix I'd say I prefer |
On Fri, Apr 12, 2019 at 01:04:29AM -0700, Eduard-Mihai Burtescu wrote:
@pnkfelix I'd say I prefer `0 => Bread(BreadKind),` but agree with your conclusion.
I like that syntax and would happily support it in place of the proposed
syntax.
|
I don't like this syntax at all. It's completely at odds with C-like enums, which means that if you currently have a C-like enum and then add a field to one of its variants, you must rewrite the entire type definition. It also puts more emphasis on the discriminant rather than the damn variant, even though the discriminants most probably aren't the most important part of the enum. It's certainly not the most important part of my use case. That syntax also makes the example from the RFC horrible: #[repr(u16)]
enum AnimationValue {
LonghandId::Color as u16 => Color(Color),
LonghandId::Height as u16 => Height(Length),
LonghandId::TransformOrigin as u16 => TransformOrigin(TransformOrigin),
} It may also be a problem to macro authors, who may want to be passed an enum definition and emit explicit discriminants for them, and now they must first check whether any of the variants have fields. Edit: But I do like a lot that @pnkfelix named his example |
On Fri, Apr 12, 2019 at 09:37:31AM -0700, Anthony Ramine wrote:
I don't like this syntax at all.
It's completely at odds with C-like enums, which means that if you currently have a C-like enum and then add a field to one of its variants, you must rewrite the entire type definition.
[...]
It may also be a problem to macro authors, who may want to be passed an enum definition and emit explicit discriminants for them, and now they must first check whether any of the variants have fields.
I'd taken it as implicit that the same syntax would be permitted for
C-like enums.
(That said, I also don't mind the `= value` synax, if people prefer that.)
|
Oh I see. That part is fine then. I still don't think it makes for a readable type definition in presence of complicated discriminant expressions. |
On Fri, Apr 12, 2019 at 10:08:03AM -0700, Anthony Ramine wrote:
> I'd taken it as implicit that the same syntax would be permitted for C-like enums.
Oh I see. That part is fine then. I still don't think it makes for a readable type definition in presence of complicated discriminant expressions.
That's fair.
I'm not sure if it's a good or bad idea to allow an "alternative"
syntax, such that both syntaxes work; on the one hand that'd allow
flexibility in writing, and on the other hand having only one syntax
makes for more consistency.
I'm happy to check off on either or both.
|
It's easier to make the other form work with
|
I'd prefer not to invent a new syntax for this feature and just keep with the obvious extension to what we have, even if its not perfect. |
Yeah, I was only replying to @pnkfelix, but I'm not proposing/in favor of a syntax different than the one in this RFC. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC will be merged soon. |
Huzzah! This RFC has been merged! Tracking issue: rust-lang/rust#60553 |
Just a few thoughs. The RFC focuses to solve some issue with the Servo design. I like the basic idea, but not the part where discriminate is a different type. If they have enums for anim, color, etc features why arent they different enums? And have a separate enum that combines all of them. I think its a design flaw in servo that shall not be fixed by an RFC. |
It is not. |
Implement arbitrary_enum_discriminant Implements RFC rust-lang/rfcs#2363 (tracking issue #60553).
Summary
This RFC gives users a way to control the discriminants of variants of all enumerations, not just the ones that are shaped like C-like enums (i.e. where all the variants have no fields).
Thanks
Thanks to Mazdak Farrokhzad (@Centril) and Simon Sapin (@SimonSapin) for the reviews, and my local bakery for their delicious baguettes. 🥖🥖
Rendered
Tracking issue