Skip to content

Commit 6278421

Browse files
committed
Address first round of comments
1 parent ad4c445 commit 6278421

File tree

1 file changed

+114
-27
lines changed

1 file changed

+114
-27
lines changed

text/0000-enum-variant-types.md

Lines changed: 114 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Summary
77
[summary]: #summary
88

9-
Enum variants are considered types in their own rights. This allows them to be irrefutably matched
9+
Consider enum variants types in their own rights. This allows them to be irrefutably matched
1010
upon. Where possible, type inference will infer variant types, but as variant types may always be
1111
treated as enum types this does not cause any issues with backwards-compatibility.
1212

@@ -25,10 +25,16 @@ println!("b = {}", b);
2525
[motivation]: #motivation
2626

2727
When working with enums, it is frequently the case that some branches of code have assurance that
28-
they are handling a particular variant of the enum. This is especially the case when abstracting
28+
they are handling a particular variant of the enum ([1], [2], [3], [4], [5], etc.). This is especially the case when abstracting
2929
behaviour for a certain enum variant. However, currently, this information is entirely hidden to the
3030
compiler and so the enum types must be matched upon even when the variant is certainly known.
3131

32+
[1]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/liballoc/borrow.rs#L245-L248
33+
[2]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/liballoc/raw_vec.rs#L424
34+
[3]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/librustc_mir/transform/simplify.rs#L162-L166
35+
[4]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/librustc_resolve/build_reduced_graph.rs#L301
36+
[5]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/librustc_resolve/macros.rs#L172-L175
37+
3238
By treating enum variants as types in their own right, this kind of abstraction is made cleaner,
3339
avoiding the need for code patterns such as:
3440
- Passing a known variant to a function, matching on it, and use `unreachable!()` arms for the other
@@ -45,41 +51,91 @@ used for type checking and permitting irrefutable matches on enum variants.
4551
[guide-level-explanation]: #guide-level-explanation
4652

4753
The variants of an enum are considered types in their own right, though they are necessarily
48-
more restricted than most user-defined types. This means that when one define an enum, one is more
54+
more restricted than most user-defined types. This means that when you define an enum, you are more
4955
precisely defining a collection of types: the enumeration itself, as well as each of its
50-
variants. However, the variant types act very similarly to the enum type in the majority of cases.
56+
variants. However, the variant types act identically to the enum type in the majority of cases.
5157

5258
Specifically, variant types act differently to enum types in the following case:
5359
- When pattern-matching on a variant type, only the constructor corresponding to the variant is
54-
considered possible. Therefore one may irrefutably pattern-match on a variant.
60+
considered possible. Therefore one may irrefutably pattern-match on a variant:
61+
62+
```rust
63+
enum Sum { A(u32), B, C }
64+
65+
fn print_A(a: Sum::A) {
66+
let A(x) = a;
67+
println!("a is {}", a);
68+
}
69+
```
70+
- One can project the fields of a variant type, similarly to tuples or structs:
71+
72+
```rust
73+
fn print_A(a: Sum::A) {
74+
println!("a is {}", a.0);
75+
}
76+
```
5577

5678
Variant types, unlike most user-defined types are subject to the following restriction:
5779
- Variant types may not have inherent impls, or implemented traits. That means `impl Enum::Variant`
5880
and `impl Trait for Enum::Variant` are forbidden. This dissuades inclinations to implement
59-
abstraction using behaviour-switching on enums, rather than using traits as is natural in Rust.
81+
abstraction using behaviour-switching on enums (for example, by simulating inheritance-based
82+
subtyping, with the enum type as the parent and each variant as children), rather than using traits
83+
as is natural in Rust.
84+
85+
```rust
86+
enum Sum { A(u32), B, C }
87+
88+
impl Sum::A { // ERROR: variant types may not have specific implementations
89+
// ...
90+
}
91+
```
92+
93+
```
94+
error[E0XXX]: variant types may not have specific implementations
95+
--> src/lib.rs:3:6
96+
|
97+
3 | impl Sum::A {
98+
| ^^^^^^
99+
| |
100+
| `Sum::A` is a variant type
101+
| help: you can try using the variant's enum: `Sum`
102+
```
103+
104+
Variant types may be aliased with type aliases:
105+
106+
```rust
107+
enum Sum { A(u32), B, C }
60108

61-
Variant types may be aliased with type aliases.
109+
type SumA = Sum::A;
110+
// `SumA` may now be used identically to `Sum::A`.
111+
```
62112

63-
If a value of a variant type is explicitly cast to the type of its enum using a type annotation or
64-
by passing it as an argument or return-value to or from a function, the variant information is lost
65-
(that is, a variant type *is* different to an enum type, even though they behave very similarly).
113+
If a value of a variant type is explicitly coerced or cast to the type of its enum using a type
114+
annotation, `as`, or by passing it as an argument or return-value to or from a function, the variant
115+
information is lost (that is, a variant type *is* different to an enum type, even though they behave
116+
similarly).
66117

67-
Note that enum types may not be coerced to variant types. Instead, matching must be performed to
68-
guarantee that the enum type truly is of the expected variant type.
118+
Note that enum types may not be coerced or cast to variant types. Instead, matching must be
119+
performed to guarantee that the enum type truly is of the expected variant type.
69120

70121
```rust
71-
enum Sum { A, B, C }
122+
enum Sum { A(u32), B, C }
72123

73124
let s: Sum = Sum::A;
74125

75126
let a = s as Sum::A; // error
76127
let a: Sum::A = s; // error
77128

78-
if let a @ Sum::A = s {
129+
if let a @ Sum::A(_) = s {
79130
// ok, `a` has type `Sum::A`
131+
println!("a is {}", a.0);
80132
}
81133
```
82134

135+
Variant types interact as expected with the proposed
136+
[generalised type ascription](https://github.com/rust-lang/rfcs/pull/2522) (i.e. the same as type
137+
coercion in `let` or similar).
138+
83139
## Type parameters
84140
Consider the following enum:
85141
```rust
@@ -90,9 +146,11 @@ enum Either<A, B> {
90146
```
91147
Here, we are defining three types: `Either`, `Either::L` and `Either::R`. However, we have to be
92148
careful here with regards to the type parameters. Specifically, the variants may not make use of
93-
every generic paramter in the enum. Since variant types are generally considered simply as enum
149+
every generic parameter in the enum. Since variant types are generally considered simply as enum
94150
types, this means that the variants need all the type information of their enums, including all
95-
their generic parameters.
151+
their generic parameters. This explictness has the advantage of preserving variance for variant
152+
types relative to their enum types, as well as permitting zero-cost coercions from variant types to
153+
enum types.
96154

97155
So, in this case, we have the types: `Either<A, B>`, `Either<A, B>::L` and `Either::<A, B>::R`.
98156

@@ -105,6 +163,10 @@ question. In most cases, the handling of `Variant` will simply delegate any beha
105163
However, pattern-matching on the variant allows irrefutable matches on the particular variant. In
106164
effect, `Variant` is only relevant to type checking/inference and the matching logic.
107165

166+
The discriminant of a `Variant` (as observed by [`discriminant_value`](https://doc.rust-lang.org/nightly/std/intrinsics/fn.discriminant_value.html)) is the discriminant
167+
of the variant (i.e. identical to the value observed if the variant is first coerced to the enum
168+
type).
169+
108170
Constructors of variants, as well as pattern-matching on particular enum variants, are now
109171
inferred to have variant types, rather than enum types.
110172

@@ -136,29 +198,42 @@ fn sum_match(s: Sum) {
136198
In essence, a value of a variant is considered to be a value of the enclosing `enum` in every matter
137199
but pattern-matching.
138200

139-
Explicitly casting to the `enum` type forgets the variant information.
201+
Explicitly coercing or casting to the `enum` type forgets the variant information.
140202

141203
```rust
142204
let x: Sum = Sum::A(5); // x: Sum
143205
let Sum::A(y) = x; // error: refutable match
206+
207+
let x = Sum::A(5) as Sum; // x: Sum
208+
let Sum::A(y) = x; // error: refutable match
144209
```
145210

146211
In all cases, the most specific type (i.e. the variant type if possible) is chosen by the type
147212
inference. However, this is entirely backwards-compatible, because `Variant` acts as `Adt` except in
148213
cases that were previously invalid (i.e. pattern-matching, where the extra typing information was
149214
previously unknown).
150215

216+
Note that because a variant type, e.g. `Sum::A`, is not a subtype of the enum type (rather, it can
217+
simply be coerced to the enum type), a type like `Vec<Sum::A>` is not a subtype of `Vec<Sum>`.
218+
(However, this should not pose a problem as it should generally be convenient to coerce `Sum::A` to
219+
`Sum` upon either formation or use.)
220+
221+
Note that we do not make any guarantees of the variant data representation at present, to allow us
222+
flexibility to explore the design space.
223+
151224
# Drawbacks
152225
[drawbacks]: #drawbacks
153226

154-
- The loose distinction between the `enum` type and its variant types could be confusing to those unfamiliar with variant types. Error messages might specifically mention a variant type, which could
227+
- The loose distinction between the `enum` type and its variant types could be confusing to those
228+
unfamiliar with variant types. Error messages might specifically mention a variant type, which could
155229
be at odds with expectations. However, since they generally behave identically, this should not
156230
prove to be a significant problem.
157231
- As variant types need to include generic parameter information that is not necessarily included in
158232
their definitions, it will be necessary to include explicit type annotations more often than is
159233
typical. Although this is unfortunate, it is necessary to preserve all the desirable behaviour of
160-
variant types described here: namely complete backwards-compatibility precise type inference
161-
(e.g. allowing `x` in `let x = Sum::A;` to have type `Sum::A` without explicit type annotations).
234+
variant types described here: namely complete backwards-compatibility precise type inference and
235+
variance (e.g. allowing `x` in `let x = Sum::A;` to have type `Sum::A` without explicit type
236+
annotations).
162237

163238
# Rationale and alternatives
164239
[rationale-and-alternatives]: #rationale-and-alternatives
@@ -167,14 +242,23 @@ The advantages of this approach are:
167242
- It naturally allows variants to be treated as types, intuitively.
168243
- It doesn't require explicit type annotations to reap the benefits of variant types.
169244
- As variant types and enum types are represented identically, there are no coercion costs.
170-
- It doesn't require type fallback.
171-
- It doesn't require value-tracking or complex type system additions such as refinement types.
245+
- It doesn't require type fallback (as was an issue with a
246+
[similar previous proposal](https://github.com/rust-lang/rfcs/pull/1450)).
247+
- It doesn't require value-tracking or complex type system additions such as
248+
[refinement types](https://en.wikipedia.org/wiki/Refinement_type).
249+
- Since complete (enum) type information is necessary for variant types, this should be forwards
250+
compatible with any extensions to enum types (e.g.
251+
[GADTs](https://en.wikipedia.org/wiki/Generalized_algebraic_data_type)).
172252

173253
One obvious alternative is to represent variant types differently to enum types and then coerce them
174254
when used as an enum. This could potentially reduce memory overhead for smaller variants
175255
(additionally no longer requiring the discriminant to be stored) and reduce the issue with providing
176256
irrelevant type parameters. However, it makes coercion more expensive and complex (as a variant
177-
could coerce to various enum types depending on the unspecified generic parameters).
257+
could coerce to various enum types depending on the unspecified generic parameters). It is proposed
258+
here that zero-cost coercions are more important. (In addition, simulating smaller variants is
259+
possible by creating separate mirroring structs for each variant for which this is desired and
260+
converting manually (though this is obviously not ideal), whereas simulating the proposed behaviour
261+
with the alternative is much more difficult, if possible at all.)
178262

179263
Variant types have [previously been proposed for Rust](https://github.com/rust-lang/rfcs/pull/1450).
180264
However, it used a more complex type inference procedure based on fallback and permitted fallible
@@ -193,9 +277,9 @@ However, it is often useful to briefly consider these variant types alone, which
193277
RFC proposes.
194278

195279
Although sum types are becoming increasingly common in programming languages, most do not choose to
196-
allow the variants to be treated as types in their own right. However, we propose that the patterns
197-
in Rust make variant types more appealing than they might be in other programming languages with
198-
variant types.
280+
allow the variants to be treated as types in their own right (that is, the author has not found
281+
any that permit this design pattern). However, we propose that the patterns in Rust make variant
282+
types more appealing than they might be in other programming languages with variant types.
199283

200284
# Unresolved questions
201285
[unresolved-questions]: #unresolved-questions
@@ -205,4 +289,7 @@ None.
205289
# Future possibilities
206290
[future-possibilities]: #future-possibilities
207291

208-
It would be possible to remove some of the restrictions on enum variant types in the future, such as permitting `impl`s or supporting variant types that don't contain all (irrelevant) generic parameters. This RFC has been written intentionally conservatively in this regard.
292+
It would be possible to remove some of the restrictions on enum variant types in the future, such as
293+
permitting `impl`s, supporting variant types that don't contain all (irrelevant) generic parameters
294+
or permitting variant types to be subtypes of enum types. This RFC has been written intentionally
295+
conservatively in this regard.

0 commit comments

Comments
 (0)