Skip to content

Commit cf7581f

Browse files
authored
Merge pull request #2363 from nox/arbitrary-enum-discriminant
Allow arbitrary enums to have explicit discriminants
2 parents 26ed27b + 93f8030 commit cf7581f

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed
+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
- Feature Name: `arbitrary_enum_discriminant`
2+
- Start Date: 2018-03-11
3+
- RFC PR: [rust-lang/rfcs#2363](https://github.com/rust-lang/rfcs/pull/2363)
4+
- Rust Issue: [rust-lang/rust#60553](https://github.com/rust-lang/rust/issues/60553)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
This RFC gives users a way to control the discriminants of variants of all
10+
enumerations, not just the ones that are shaped like C-like enums (i.e. where
11+
all the variants have no fields).
12+
13+
The change is minimal: allow any variant to be adorned with an explicit
14+
discriminant value, whether or not that variant has any field.
15+
16+
# Motivation
17+
[motivation]: #motivation
18+
19+
Stylo, the style system of Servo, represents CSS properties with a large
20+
enumeration `PropertyDeclaration` where each variant has only one field which
21+
represents the value of a given CSS property. Here is a subset of it:
22+
23+
```rust
24+
#[repr(u16)]
25+
enum PropertyDeclaration {
26+
Color(Color),
27+
Height(Length),
28+
InlineSize(Length),
29+
TransformOrigin(TransformOrigin),
30+
}
31+
```
32+
33+
For various book-keeping reasons, Servo also generates a `LonghandId`
34+
enumeration with the same variants as `PropertyDeclaration` but without the
35+
fields, thus making `LonghandId` a C-like enumeration:
36+
37+
```rust
38+
#[derive(Clone, Copy)]
39+
#[repr(u16)]
40+
enum LonghandId {
41+
Color,
42+
Height,
43+
InlineSize,
44+
TransformOrigin,
45+
}
46+
```
47+
48+
Given that rustc guarantees that `#[repr(u16)]` enumerations start with their
49+
discriminant stored as a `u16`, going from `&PropertyDeclaration` to
50+
`LonghandId` is then just a matter of unsafely coercing `&self` as a
51+
`&LonghandId`:
52+
53+
```rust
54+
impl PropertyDeclaration {
55+
fn id(&self) -> LonghandId {
56+
unsafe { *(self as *const Self as *const LonghandId) }
57+
}
58+
}
59+
```
60+
61+
This works great, but doesn't scale if we want to replicate this behaviour for
62+
an enumeration that is a subset of `PropertyDeclaration`, for example an
63+
enumeration `AnimationValue` that is limited to animatable properties:
64+
65+
```rust
66+
#[repr(u16)]
67+
enum AnimationValue {
68+
Color(Color),
69+
Height(Length),
70+
TransformOrigin(TransformOrigin),
71+
}
72+
73+
impl AnimationValue {
74+
fn id(&self) -> LonghandId {
75+
// We can't just unsafely read `&self` as a `&LonghandId` because
76+
// the discriminant of `AnimationValue::TransformOrigin` isn't equal
77+
// to `LonghandId::TransformOrigin` anymore.
78+
match *self {
79+
AnimationValue::Color(_) => LonghandId::Color,
80+
AnimationValue::Height(_) => LonghandId::Height,
81+
AnimationValue::TransformOrigin(_) => LonghandId::TransformOrigin,
82+
}
83+
}
84+
}
85+
```
86+
87+
This is not sustainable, as the jump table generated by rustc to compile this
88+
huge match expression is larger than 4KB in the final Gecko binary, when this
89+
operation could be a trivial `u16` copy. This is worked around in Servo by
90+
generating spurious `Void` variants for the non-animatable properties in
91+
`AnimationValue`:
92+
93+
```rust
94+
enum Void {}
95+
96+
#[repr(u16)]
97+
enum AnimationValue {
98+
Color(Color),
99+
Height(Length),
100+
InlineSize(Void),
101+
TransformOrigin(TransformOrigin),
102+
}
103+
104+
impl AnimationValue {
105+
fn id(&self) -> LonghandId
106+
// We can use the unsafe trick again.
107+
unsafe { *(self as *const Self as *const LonghandId) }
108+
}
109+
}
110+
```
111+
112+
This is unfortunately quite painful to use, given now all methods matching
113+
against `AnimationValue` need to have dummy arms for all of these variants:
114+
115+
```rust
116+
impl AnimationValue {
117+
fn do_something(&self) {
118+
match *self {
119+
AnimationValue::Color(ref color) => {
120+
do_something_with_color(color)
121+
}
122+
AnimationValue::Height(ref height) => {
123+
do_something_with_height(height)
124+
}
125+
// This shouldn't be needed.
126+
AnimationValue::InlineSize(ref void) => {
127+
match *void {}
128+
}
129+
AnimationValue::TransformOrigin(ref origin) => {
130+
do_something_with_transform_origin(origin)
131+
}
132+
}
133+
}
134+
}
135+
```
136+
137+
We suggest generalising the explicit discriminant notation to all enums,
138+
regardless of whether their variants have fields or not:
139+
140+
```rust
141+
#[repr(u16)]
142+
enum AnimationValue {
143+
Color(Color) = LonghandId::Color as u16,
144+
Height(Length) = LonghandId::Height as u16,
145+
TransformOrigin(TransformOrigin) = LonghandId::TransformOrigin as u16,
146+
}
147+
148+
impl AnimationValue {
149+
fn id(&self) -> LonghandId
150+
// We can use the unsafe trick again.
151+
unsafe { *(self as *const Self as *const LonghandId) }
152+
}
153+
154+
fn do_something(&self) {
155+
// No spurious variant anymore.
156+
match *self {
157+
AnimationValue::Color(ref color) => {
158+
do_something_with_color(color)
159+
}
160+
AnimationValue::Height(ref height) => {
161+
do_something_with_height(height)
162+
}
163+
AnimationValue::TransformOrigin(ref origin) => {
164+
do_something_with_transform_origin(origin)
165+
}
166+
}
167+
}
168+
}
169+
```
170+
171+
# Guide-level explanation
172+
[guide-level-explanation]: #guide-level-explanation
173+
174+
An enumeration with only field-less variants can currently have explicit
175+
discriminant values:
176+
177+
```rust
178+
enum ForceFromage {
179+
Emmental = 0,
180+
Camembert = 1,
181+
Roquefort = 2,
182+
}
183+
```
184+
185+
With this RFC, users are allowed to put explicit discriminant values on any
186+
variant of any enumeration, not just the ones where all variants are field-less:
187+
188+
```rust
189+
enum ParisianSandwichIngredient {
190+
Bread(BreadKind) = 0,
191+
Ham(HamKind) = 1,
192+
Butter(ButterKind) = 2,
193+
}
194+
```
195+
196+
# Reference-level explanation
197+
[reference-level-explanation]: #reference-level-explanation
198+
199+
## Grammar
200+
201+
The production for enumeration items becomes:
202+
203+
```
204+
EnumItem :
205+
OuterAttribute*
206+
IDENTIFIER ( EnumItemTuple | EnumItemStruct)? EnumItemDiscriminant?
207+
```
208+
209+
## Semantics
210+
211+
The limitation that only field-less enumerations can have explicit discriminant
212+
values is lifted, and no other change is made to their semantics:
213+
214+
* enumerations with fields still can't be casted to numeric types
215+
with the `as` operator;
216+
* if the first variant doesn't have an explicit discriminant,
217+
it is set to zero;
218+
* any unspecified discriminant is set to one higher than the one from
219+
the previous variant;
220+
* under the default representation, the specified discriminants are
221+
interpreted as `isize`;
222+
* two variants cannot share the same discriminant.
223+
224+
# Drawbacks
225+
[drawbacks]: #drawbacks
226+
227+
This introduces one more knob to the representation of enumerations.
228+
229+
# Rationale and alternatives
230+
[alternatives]: #alternatives
231+
232+
Reusing the current syntax and semantics for explicit discriminants of
233+
field-less enumerations means that the changes to the grammar and semantics of
234+
the language are minimal. There are a few possible alternatives nonetheless.
235+
236+
## Discriminant values in attributes
237+
238+
We could specify the discriminant values in variant attributes, but this would
239+
be at odds with the syntax for field-less enumerations.
240+
241+
```rust
242+
enum ParisianSandwichIngredient {
243+
#[discriminant = 0]
244+
Bread(BreadKind),
245+
#[discriminant = 1]
246+
Ham(HamKind),
247+
#[discriminant = 2]
248+
Butter(ButterKind),
249+
}
250+
```
251+
252+
## Use discriminants of a separate field-less enumeration
253+
254+
We could tell rustc to tie the discriminants of the enumeration to the
255+
variants of a separate field-less enumeration.
256+
257+
```rust
258+
#[discriminant(IngredientKind)]
259+
enum ParisianSandwichIngredient {
260+
Bread(BreadKind),
261+
Ham(HamKind),
262+
Butter(ButterKind),
263+
}
264+
265+
enum IngredientKind {
266+
Bread,
267+
Ham,
268+
Butter,
269+
}
270+
```
271+
272+
This isn't applicable if such a separate field-less enumeration doesn't exist,
273+
and this can easily be done as a procedural macro using the feature described
274+
in this RFC. It also looks way more like spooky action at a distance.
275+
276+
# Prior art
277+
[prior-art]: #prior-art
278+
279+
No prior art.
280+
281+
# Unresolved questions
282+
[unresolved]: #unresolved-questions
283+
284+
## Should discriminants of enumerations with fields be specified as variant attributes?
285+
286+
Should they?
287+
288+
## Should this apply only to enumerations with an explicit representation?
289+
290+
Should it?
291+
292+
# Thanks
293+
294+
Thanks to Mazdak Farrokhzad (@Centril) and Simon Sapin (@SimonSapin) for
295+
the reviews, and my local bakery for their delicious baguettes. 🥖

0 commit comments

Comments
 (0)