From b9b9cd18f86c964535bcea1f0cf08b4628557bb1 Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 3 Nov 2018 23:55:29 +0000 Subject: [PATCH 01/14] Initial draft for enum variant types --- text/0000-enum-variant-types.md | 203 ++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 text/0000-enum-variant-types.md diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md new file mode 100644 index 00000000000..798675c2246 --- /dev/null +++ b/text/0000-enum-variant-types.md @@ -0,0 +1,203 @@ +- Feature Name: `enum_variant_types` +- Start Date: 03-11-2018 +- RFC PR: +- Rust Issue: + +# Summary +[summary]: #summary + +Enum variants are considered types in their own rights. This allows them to be irrefutably matched +upon. Where possible, type inference will infer variant types, but as variant types may always be +treated as enum types this does not cause any issues with backwards-compatibility. + +```rust +enum Either { L(A), R(B) } + +fn all_right(b: B) -> Either::R { + Either::R(b) +} + +let Either::R(b) = all_right::<(), _>(1729); +println!("b = {}", b); +``` + +# Motivation +[motivation]: #motivation + +When working with enums, it is frequently the case that some branches of code have assurance that +they are handling a particular variant of the enum. This is especially the case when abstracting +behaviour for a certain enum variant. However, currently, this information is entirely hidden to the +compiler and so the enum types must be matched upon even when the variant is certainly known. + +By treating enum variants as types in their own right, this kind of abstraction is made cleaner, +avoiding the need for code patterns such as: +- Passing a known variant to a function, matching on it, and use `unreachable!()` arms for the other +variants. +- Passing individual fields from the variant to a function. +- Duplicating a variant as a standalone `struct`. + +However, though abstracting behaviour for specific variants is often convenient, it is understood +that such variants are intended to be treated as enums in general. As such, the variant types +proposed here have identical representations to their enums; the extra type information is simply +used for type checking and permitting irrefutable matches on enum variants. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The variants of an enum are considered types in their own right, though they are necessarily +more restricted than most user-defined types. This means that when one define an enum, one is more +precisely defining a collection of types: the enumeration itself, as well as each of its +variants. However, the variant types act very similarly to the enum type in the majority of cases. + +Specifically, variant types act differently to enum types in the following case: +- When pattern-matching on a variant type, only the constructor corresponding to the variant is +considered possible. Therefore one may irrefutably pattern-match on a variant. + +Variant types, unlike most user-defined types are subject to the following restriction: +- Variant types may not have inherent impls, or implemented traits. That means `impl Enum::Variant` +and `impl Trait for Enum::Variant` are forbidden. This dissuades inclinations to implement +abstraction using behaviour-switching on enums, rather than using traits as is natural in Rust. + +Variant types may be aliased with type aliases. + +If a value of a variant type is explicitly cast to the type of its enum using a type annotation or +by passing it as an argument or return-value to or from a function, the variant information is lost +(that is, a variant type *is* different to an enum type, even though they behave very similarly). + +Note that enum types may not be coerced to variant types. Instead, matching must be performed to +guarantee that the enum type truly is of the expected variant type. + +```rust +enum Sum { A, B, C } + +let s: Sum = Sum::A; + +let a = s as Sum::A; // error +let a: Sum::A = s; // error + +if let a @ Sum::A = s { + // ok, `a` has type `Sum::A` +} +``` + +## Type parameters +Consider the following enum: +```rust +enum Either { + L(A), + R(B), +} +``` +Here, we are defining three types: `Either`, `Either::L` and `Either::R`. However, we have to be +careful here with regards to the type parameters. Specifically, the variants may not make use of +every generic paramter in the enum. Since variant types are generally considered simply as enum +types, this means that the variants need all the type information of their enums, including all +their generic parameters. + +So, in this case, we have the types: `Either`, `Either::L` and `Either::::R`. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +A new variant, `Variant(DefId, VariantDiscr)`, will be added to `TyKind`, whose `DefId` points to +the enclosing enum for the variant and `VariantDiscr` is the discriminant for the variant in +question. In most cases, the handling of `Variant` will simply delegate any behaviour to its `enum`. +However, pattern-matching on the variant allows irrefutable matches on the particular variant. In +effect, `Variant` is only relevant to type checking/inference and the matching logic. + +Constructors of variants, as well as pattern-matching on particular enum variants, are now +inferred to have variant types, rather than enum types. + +```rust +enum Sum { + A(u8), + B, + C, +} + +let x = Sum::A(5); // x: Sum::A +let Sum::A(y) = x; // ok, y = 5 + +fn sum_match(s: Sum) { + match s { + a @ Sum::A(_) => { + let x = a; // ok, a: Sum::A + } + b @ Sum::B => { + // b: Sum::B + } + c @ Sum::C => { + // c: Sum::C + } + } +} +``` + +In essence, a value of a variant is considered to be a value of the enclosing `enum` in every matter +but pattern-matching. + +Explicitly casting to the `enum` type forgets the variant information. + +```rust +let x: Sum = Sum::A(5); // x: Sum +let Sum::A(y) = x; // error: refutable match +``` + +In all cases, the most specific type (i.e. the variant type if possible) is chosen by the type +inference. However, this is entirely backwards-compatible, because `Variant` acts as `Adt` except in +cases that were previously invalid (i.e. pattern-matching, where the extra typing information was +previously unknown). + +# Drawbacks +[drawbacks]: #drawbacks + +- 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 +be at odds with expectations. However, since they generally behave identically, this should not +prove to be a significant problem. +- As variant types need to include generic parameter information that is not necessarily included in +their definitions, it will be necessary to include explicit type annotations more often than is +typical. Although this is unfortunate, it is necessary to preserve all the desirable behaviour of +variant types described here: namely complete backwards-compatibility precise type inference +(e.g. allowing `x` in `let x = Sum::A;` to have type `Sum::A` without explicit type annotations). + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +The advantages of this approach are: +- It naturally allows variants to be treated as types, intuitively. +- It doesn't require explicit type annotations to reap the benefits of variant types. +- As variant types and enum types are represented identically, there are no coercion costs. +- It doesn't require type fallback. +- It doesn't require value-tracking or complex type system additions such as refinement types. + +One obvious alternative is to represent variant types differently to enum types and then coerce them +when used as an enum. This could potentially reduce memory overhead for smaller variants +(additionally no longer requiring the discriminant to be stored) and reduce the issue with providing +irrelevant type parameters. However, it makes coercion more expensive and complex (as a variant +could coerce to various enum types depending on the unspecified generic parameters). + +Variant types have [previously been proposed for Rust](https://github.com/rust-lang/rfcs/pull/1450). +However, it used a more complex type inference procedure based on fallback and permitted fallible +coercion to variant types. The method proposed here is implementationally simpler and more +intuitive. + +# Prior art +[prior-art]: #prior-art + +Type-theoretically, enums are sum types. A sum type `S := A + B` is a type, `S`, defined in relation +to two other types, `A` and `B`. Variants are specifically types, but in programming it's usually +useful to consider particular variants in relation to each other, rather than standalone (which is +why `enum` *defines* types for its variants rather than using pre-existing types for its variants). + +However, it is often useful to briefly consider these variant types alone, which is what this +RFC proposes. + +Although sum types are becoming increasingly common in programming languages, most do not choose to +allow the variants to be treated as types in their own right. However, we propose that the patterns +in Rust make variant types more appealing than they might be in other programming languages with +variant types. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- None. From ad4c445d9253084f413ec8ca2b0286f04b2c7980 Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 10 Nov 2018 14:35:24 +0000 Subject: [PATCH 02/14] Add a Future possibilities section --- text/0000-enum-variant-types.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index 798675c2246..e55672c98fe 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -200,4 +200,9 @@ variant types. # Unresolved questions [unresolved-questions]: #unresolved-questions -- None. +None. + +# Future possibilities +[future-possibilities]: #future-possibilities + +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. From 6278421622ae6d90465ca3b16c01e8693d1deb3a Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 10 Nov 2018 16:00:44 +0000 Subject: [PATCH 03/14] Address first round of comments --- text/0000-enum-variant-types.md | 141 ++++++++++++++++++++++++++------ 1 file changed, 114 insertions(+), 27 deletions(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index e55672c98fe..249911bda10 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -Enum variants are considered types in their own rights. This allows them to be irrefutably matched +Consider enum variants types in their own rights. This allows them to be irrefutably matched upon. Where possible, type inference will infer variant types, but as variant types may always be treated as enum types this does not cause any issues with backwards-compatibility. @@ -25,10 +25,16 @@ println!("b = {}", b); [motivation]: #motivation When working with enums, it is frequently the case that some branches of code have assurance that -they are handling a particular variant of the enum. This is especially the case when abstracting +they are handling a particular variant of the enum ([1], [2], [3], [4], [5], etc.). This is especially the case when abstracting behaviour for a certain enum variant. However, currently, this information is entirely hidden to the compiler and so the enum types must be matched upon even when the variant is certainly known. +[1]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/liballoc/borrow.rs#L245-L248 +[2]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/liballoc/raw_vec.rs#L424 +[3]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/librustc_mir/transform/simplify.rs#L162-L166 +[4]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/librustc_resolve/build_reduced_graph.rs#L301 +[5]: https://github.com/rust-lang/rust/blob/69a04a19d1274ce73354ba775687e126d1d59fdd/src/librustc_resolve/macros.rs#L172-L175 + By treating enum variants as types in their own right, this kind of abstraction is made cleaner, avoiding the need for code patterns such as: - 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. [guide-level-explanation]: #guide-level-explanation The variants of an enum are considered types in their own right, though they are necessarily -more restricted than most user-defined types. This means that when one define an enum, one is more +more restricted than most user-defined types. This means that when you define an enum, you are more precisely defining a collection of types: the enumeration itself, as well as each of its -variants. However, the variant types act very similarly to the enum type in the majority of cases. +variants. However, the variant types act identically to the enum type in the majority of cases. Specifically, variant types act differently to enum types in the following case: - When pattern-matching on a variant type, only the constructor corresponding to the variant is -considered possible. Therefore one may irrefutably pattern-match on a variant. +considered possible. Therefore one may irrefutably pattern-match on a variant: + +```rust +enum Sum { A(u32), B, C } + +fn print_A(a: Sum::A) { + let A(x) = a; + println!("a is {}", a); +} +``` +- One can project the fields of a variant type, similarly to tuples or structs: + +```rust +fn print_A(a: Sum::A) { + println!("a is {}", a.0); +} +``` Variant types, unlike most user-defined types are subject to the following restriction: - Variant types may not have inherent impls, or implemented traits. That means `impl Enum::Variant` and `impl Trait for Enum::Variant` are forbidden. This dissuades inclinations to implement -abstraction using behaviour-switching on enums, rather than using traits as is natural in Rust. +abstraction using behaviour-switching on enums (for example, by simulating inheritance-based +subtyping, with the enum type as the parent and each variant as children), rather than using traits +as is natural in Rust. + +```rust +enum Sum { A(u32), B, C } + +impl Sum::A { // ERROR: variant types may not have specific implementations + // ... +} +``` + +``` +error[E0XXX]: variant types may not have specific implementations + --> src/lib.rs:3:6 + | +3 | impl Sum::A { + | ^^^^^^ + | | + | `Sum::A` is a variant type + | help: you can try using the variant's enum: `Sum` +``` + +Variant types may be aliased with type aliases: + +```rust +enum Sum { A(u32), B, C } -Variant types may be aliased with type aliases. +type SumA = Sum::A; +// `SumA` may now be used identically to `Sum::A`. +``` -If a value of a variant type is explicitly cast to the type of its enum using a type annotation or -by passing it as an argument or return-value to or from a function, the variant information is lost -(that is, a variant type *is* different to an enum type, even though they behave very similarly). +If a value of a variant type is explicitly coerced or cast to the type of its enum using a type +annotation, `as`, or by passing it as an argument or return-value to or from a function, the variant +information is lost (that is, a variant type *is* different to an enum type, even though they behave +similarly). -Note that enum types may not be coerced to variant types. Instead, matching must be performed to -guarantee that the enum type truly is of the expected variant type. +Note that enum types may not be coerced or cast to variant types. Instead, matching must be +performed to guarantee that the enum type truly is of the expected variant type. ```rust -enum Sum { A, B, C } +enum Sum { A(u32), B, C } let s: Sum = Sum::A; let a = s as Sum::A; // error let a: Sum::A = s; // error -if let a @ Sum::A = s { +if let a @ Sum::A(_) = s { // ok, `a` has type `Sum::A` + println!("a is {}", a.0); } ``` +Variant types interact as expected with the proposed +[generalised type ascription](https://github.com/rust-lang/rfcs/pull/2522) (i.e. the same as type +coercion in `let` or similar). + ## Type parameters Consider the following enum: ```rust @@ -90,9 +146,11 @@ enum Either { ``` Here, we are defining three types: `Either`, `Either::L` and `Either::R`. However, we have to be careful here with regards to the type parameters. Specifically, the variants may not make use of -every generic paramter in the enum. Since variant types are generally considered simply as enum +every generic parameter in the enum. Since variant types are generally considered simply as enum types, this means that the variants need all the type information of their enums, including all -their generic parameters. +their generic parameters. This explictness has the advantage of preserving variance for variant +types relative to their enum types, as well as permitting zero-cost coercions from variant types to +enum types. So, in this case, we have the types: `Either`, `Either::L` and `Either::::R`. @@ -105,6 +163,10 @@ question. In most cases, the handling of `Variant` will simply delegate any beha However, pattern-matching on the variant allows irrefutable matches on the particular variant. In effect, `Variant` is only relevant to type checking/inference and the matching logic. +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 +of the variant (i.e. identical to the value observed if the variant is first coerced to the enum +type). + Constructors of variants, as well as pattern-matching on particular enum variants, are now inferred to have variant types, rather than enum types. @@ -136,11 +198,14 @@ fn sum_match(s: Sum) { In essence, a value of a variant is considered to be a value of the enclosing `enum` in every matter but pattern-matching. -Explicitly casting to the `enum` type forgets the variant information. +Explicitly coercing or casting to the `enum` type forgets the variant information. ```rust let x: Sum = Sum::A(5); // x: Sum let Sum::A(y) = x; // error: refutable match + +let x = Sum::A(5) as Sum; // x: Sum +let Sum::A(y) = x; // error: refutable match ``` In all cases, the most specific type (i.e. the variant type if possible) is chosen by the type @@ -148,17 +213,27 @@ inference. However, this is entirely backwards-compatible, because `Variant` act cases that were previously invalid (i.e. pattern-matching, where the extra typing information was previously unknown). +Note that because a variant type, e.g. `Sum::A`, is not a subtype of the enum type (rather, it can +simply be coerced to the enum type), a type like `Vec` is not a subtype of `Vec`. +(However, this should not pose a problem as it should generally be convenient to coerce `Sum::A` to +`Sum` upon either formation or use.) + +Note that we do not make any guarantees of the variant data representation at present, to allow us +flexibility to explore the design space. + # Drawbacks [drawbacks]: #drawbacks -- 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 +- 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 be at odds with expectations. However, since they generally behave identically, this should not prove to be a significant problem. - As variant types need to include generic parameter information that is not necessarily included in their definitions, it will be necessary to include explicit type annotations more often than is typical. Although this is unfortunate, it is necessary to preserve all the desirable behaviour of -variant types described here: namely complete backwards-compatibility precise type inference -(e.g. allowing `x` in `let x = Sum::A;` to have type `Sum::A` without explicit type annotations). +variant types described here: namely complete backwards-compatibility precise type inference and +variance (e.g. allowing `x` in `let x = Sum::A;` to have type `Sum::A` without explicit type +annotations). # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -167,14 +242,23 @@ The advantages of this approach are: - It naturally allows variants to be treated as types, intuitively. - It doesn't require explicit type annotations to reap the benefits of variant types. - As variant types and enum types are represented identically, there are no coercion costs. -- It doesn't require type fallback. -- It doesn't require value-tracking or complex type system additions such as refinement types. +- It doesn't require type fallback (as was an issue with a +[similar previous proposal](https://github.com/rust-lang/rfcs/pull/1450)). +- It doesn't require value-tracking or complex type system additions such as +[refinement types](https://en.wikipedia.org/wiki/Refinement_type). +- Since complete (enum) type information is necessary for variant types, this should be forwards +compatible with any extensions to enum types (e.g. +[GADTs](https://en.wikipedia.org/wiki/Generalized_algebraic_data_type)). One obvious alternative is to represent variant types differently to enum types and then coerce them when used as an enum. This could potentially reduce memory overhead for smaller variants (additionally no longer requiring the discriminant to be stored) and reduce the issue with providing irrelevant type parameters. However, it makes coercion more expensive and complex (as a variant -could coerce to various enum types depending on the unspecified generic parameters). +could coerce to various enum types depending on the unspecified generic parameters). It is proposed +here that zero-cost coercions are more important. (In addition, simulating smaller variants is +possible by creating separate mirroring structs for each variant for which this is desired and +converting manually (though this is obviously not ideal), whereas simulating the proposed behaviour +with the alternative is much more difficult, if possible at all.) Variant types have [previously been proposed for Rust](https://github.com/rust-lang/rfcs/pull/1450). 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 RFC proposes. Although sum types are becoming increasingly common in programming languages, most do not choose to -allow the variants to be treated as types in their own right. However, we propose that the patterns -in Rust make variant types more appealing than they might be in other programming languages with -variant types. +allow the variants to be treated as types in their own right (that is, the author has not found +any that permit this design pattern). However, we propose that the patterns in Rust make variant +types more appealing than they might be in other programming languages with variant types. # Unresolved questions [unresolved-questions]: #unresolved-questions @@ -205,4 +289,7 @@ None. # Future possibilities [future-possibilities]: #future-possibilities -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. +It would be possible to remove some of the restrictions on enum variant types in the future, such as +permitting `impl`s, supporting variant types that don't contain all (irrelevant) generic parameters +or permitting variant types to be subtypes of enum types. This RFC has been written intentionally +conservatively in this regard. From e64df31c4cef6b5232b7675984b75f3ef147bbfc Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 10 Nov 2018 16:37:13 +0000 Subject: [PATCH 04/14] Acquiesce to Centril's hatred of one --- text/0000-enum-variant-types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index 249911bda10..f24360b351e 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -57,7 +57,7 @@ variants. However, the variant types act identically to the enum type in the maj Specifically, variant types act differently to enum types in the following case: - When pattern-matching on a variant type, only the constructor corresponding to the variant is -considered possible. Therefore one may irrefutably pattern-match on a variant: +considered possible. Therefore you may irrefutably pattern-match on a variant: ```rust enum Sum { A(u32), B, C } @@ -67,7 +67,7 @@ fn print_A(a: Sum::A) { println!("a is {}", a); } ``` -- One can project the fields of a variant type, similarly to tuples or structs: +- You may project the fields of a variant type, similarly to tuples or structs: ```rust fn print_A(a: Sum::A) { From 507464c5303c53b0f019ed01d337bd6db4630b06 Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 10 Nov 2018 16:40:26 +0000 Subject: [PATCH 05/14] Add extra clarification --- text/0000-enum-variant-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index f24360b351e..85063dbafc5 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -219,7 +219,7 @@ simply be coerced to the enum type), a type like `Vec` is not a subtype `Sum` upon either formation or use.) Note that we do not make any guarantees of the variant data representation at present, to allow us -flexibility to explore the design space. +flexibility to explore the design space in terms of trade-offs between memory and performance. # Drawbacks [drawbacks]: #drawbacks From 508b43b339a1bd2444f7de297f7ebb147a649de2 Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 10 Nov 2018 17:18:12 +0000 Subject: [PATCH 06/14] Update the start date --- text/0000-enum-variant-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index 85063dbafc5..d3626a69f56 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -1,5 +1,5 @@ - Feature Name: `enum_variant_types` -- Start Date: 03-11-2018 +- Start Date: 10-11-2018 - RFC PR: - Rust Issue: From f9451c3226c598cfd7d2dd1b6bcee11ef291ecbd Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 10 Nov 2018 18:58:00 +0000 Subject: [PATCH 07/14] Mention refutable matching on variant types --- text/0000-enum-variant-types.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index d3626a69f56..afbc18e393c 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -64,9 +64,22 @@ enum Sum { A(u32), B, C } fn print_A(a: Sum::A) { let A(x) = a; - println!("a is {}", a); + println!("a is {}", x); } ``` +However, in order to be backwards-compatible with existing handling of variants as enums, matches on +variant types will permit (and simply ignore) arms that correspond to other variants: + +```rust +let a = Sum::A(20); + +match a { + A(x) => println!("a is {}", x), + B => println!("a is B"), // ok, but unreachable + C => println!("a is C"), // ok, but unreachable +} +``` + - You may project the fields of a variant type, similarly to tuples or structs: ```rust From 2b00420c80e16e947f25a3072fbfaa94f7038ea0 Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 10 Nov 2018 18:58:57 +0000 Subject: [PATCH 08/14] Mention Scala's Either type --- text/0000-enum-variant-types.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index afbc18e393c..b34615c7c5b 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -290,9 +290,11 @@ However, it is often useful to briefly consider these variant types alone, which RFC proposes. Although sum types are becoming increasingly common in programming languages, most do not choose to -allow the variants to be treated as types in their own right (that is, the author has not found -any that permit this design pattern). However, we propose that the patterns in Rust make variant -types more appealing than they might be in other programming languages with variant types. +allow the variants to be treated as types in their own right. There are some languages that have +analogues however: Scala's [`Either` type](https://www.scala-lang.org/api/2.9.3/scala/Either.html) +has `Left` and `Right` subclasses that may be treated as standalone types, for instance. Regardless +of the scarcity of variant types however, we propose that the patterns in Rust make variant types +more appealing than they might be in other programming languages with variant types. # Unresolved questions [unresolved-questions]: #unresolved-questions From 20e6d9d7da368c019d9281d9a68db92c11734e00 Mon Sep 17 00:00:00 2001 From: varkor Date: Mon, 19 Nov 2018 17:55:38 +0000 Subject: [PATCH 09/14] Minor clarifications --- text/0000-enum-variant-types.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index b34615c7c5b..7a32573cfbd 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -67,7 +67,7 @@ fn print_A(a: Sum::A) { println!("a is {}", x); } ``` -However, in order to be backwards-compatible with existing handling of variants as enums, matches on +However, to be backwards-compatible with existing handling of variants as enums, matches on variant types will permit (and simply ignore) arms that correspond to other variants: ```rust @@ -80,6 +80,9 @@ match a { } ``` +To avoid this behaviour, a new lint, `strict_variant_matching` will be added that will forbid +matching on other variants. + - You may project the fields of a variant type, similarly to tuples or structs: ```rust @@ -145,6 +148,9 @@ if let a @ Sum::A(_) = s { } ``` +If multiple variants are bound with a single binding variable `x`, then the type of `x` will simply +be the type of the enum, as before (i.e. binding on variants must be unambiguous). + Variant types interact as expected with the proposed [generalised type ascription](https://github.com/rust-lang/rfcs/pull/2522) (i.e. the same as type coercion in `let` or similar). From 56f71e6d2e89c9056b4c74c9df776cea228c5986 Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 21 Dec 2018 17:50:08 +0000 Subject: [PATCH 10/14] Switch to a type-inference based method --- text/0000-enum-variant-types.md | 38 ++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index 7a32573cfbd..2055ad49c38 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -227,10 +227,27 @@ let x = Sum::A(5) as Sum; // x: Sum let Sum::A(y) = x; // error: refutable match ``` -In all cases, the most specific type (i.e. the variant type if possible) is chosen by the type -inference. However, this is entirely backwards-compatible, because `Variant` acts as `Adt` except in -cases that were previously invalid (i.e. pattern-matching, where the extra typing information was -previously unknown). +Inferring the type of an `enum` or variant is now slightly more involved. A new kind of inference +variable, for variants, will be added (akin to numeric type inference). The type for an `enum` or +variant will be chosen according to the following rules: +- If the value is treated as a single variant (and possibly additionally as the `enum`), we choose +the single variant type. For example: +```rust +let x = Sum::A(5); // x: Sum::A +println!("x is {}", x.0); +``` +- If the type is treated as multiple different variants, we choose the `enum` type. +```rust +let mut x = Sum::A(5); // x: Sum +println!("x is {}", x.0); // error: no field `0` on type `Sum` +x = Sum::B; +println!("x is not numeric"); +``` +- In a case where the type variable is unknown, we default to the `enum`. + +This is backwards-compatible with existing code, as variants act as `enum`s except in cases that +were previously invalid (i.e. pattern-matching, where the extra typing information was previously +unknown). Note that because a variant type, e.g. `Sum::A`, is not a subtype of the enum type (rather, it can simply be coerced to the enum type), a type like `Vec` is not a subtype of `Vec`. @@ -261,9 +278,8 @@ The advantages of this approach are: - It naturally allows variants to be treated as types, intuitively. - It doesn't require explicit type annotations to reap the benefits of variant types. - As variant types and enum types are represented identically, there are no coercion costs. -- It doesn't require type fallback (as was an issue with a -[similar previous proposal](https://github.com/rust-lang/rfcs/pull/1450)). -- It doesn't require value-tracking or complex type system additions such as +- It doesn't require value-tracking (save the degenerate kind performed by type inference) +or complex type system additions such as [refinement types](https://en.wikipedia.org/wiki/Refinement_type). - Since complete (enum) type information is necessary for variant types, this should be forwards compatible with any extensions to enum types (e.g. @@ -280,9 +296,11 @@ converting manually (though this is obviously not ideal), whereas simulating the with the alternative is much more difficult, if possible at all.) Variant types have [previously been proposed for Rust](https://github.com/rust-lang/rfcs/pull/1450). -However, it used a more complex type inference procedure based on fallback and permitted fallible -coercion to variant types. The method proposed here is implementationally simpler and more -intuitive. +However, it was closed due to uncertainty around the interaction with other forms of type inference +(numeric type inference and default type parameters). Having looked into these, I think adding an +extra kind of type inference for enum variants should not directly interact with these and it is +sensible to open up this RFC without more substantial changes to the proposed implementation method. +Furthermore, the method proposed here is implementationally simpler and more intuitive. # Prior art [prior-art]: #prior-art From 415af1fdaad5b684862aaeef1a84a0b4c0a5897d Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 21 Dec 2018 18:06:17 +0000 Subject: [PATCH 11/14] Mention space-optimised enum variant types --- text/0000-enum-variant-types.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index 2055ad49c38..146af3c8d1b 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -332,3 +332,7 @@ It would be possible to remove some of the restrictions on enum variant types in permitting `impl`s, supporting variant types that don't contain all (irrelevant) generic parameters or permitting variant types to be subtypes of enum types. This RFC has been written intentionally conservatively in this regard. + +In addition, we could offer a way to space-optimise variant types (rather than minimising +conversion costs). By not committing to a specific representation now, this allows us to make a +decision as to how to support this use case in the future, possibly through attributes on the enum. From c821c2972df3efa3e18861ecddeb6c00df433476 Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 21 Dec 2018 18:22:04 +0000 Subject: [PATCH 12/14] Reference optimize(size) --- text/0000-enum-variant-types.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index 146af3c8d1b..363bb808813 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -335,4 +335,6 @@ conservatively in this regard. In addition, we could offer a way to space-optimise variant types (rather than minimising conversion costs). By not committing to a specific representation now, this allows us to make a -decision as to how to support this use case in the future, possibly through attributes on the enum. +decision as to how to support this use case in the future, possibly through attributes on the enum, +such as the [`#[optimize(size)]` attribute](https://github.com/rust-lang/rfcs/pull/2412); or through +anonymous enum types, which often come up in such discussions. From 9e34d3043adf19144729404700e9c9d4a8055693 Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 22 Dec 2018 11:45:51 +0000 Subject: [PATCH 13/14] Add impls as an unanswered question --- text/0000-enum-variant-types.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index 363bb808813..84026d5baca 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -323,7 +323,8 @@ more appealing than they might be in other programming languages with variant ty # Unresolved questions [unresolved-questions]: #unresolved-questions -None. +- Is disallowing `impl`s on variant types too conservative or restrictive? Should we instead permit +them (and potentially provide clippy lints to point out unidiomatic patterns). # Future possibilities [future-possibilities]: #future-possibilities From d4b6187d0329b3e689d725375c931b290d2b3f4c Mon Sep 17 00:00:00 2001 From: varkor Date: Wed, 27 Mar 2019 00:21:48 +0000 Subject: [PATCH 14/14] More conservative type inference --- text/0000-enum-variant-types.md | 39 ++++++++++++--------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/text/0000-enum-variant-types.md b/text/0000-enum-variant-types.md index 84026d5baca..bf6b6084de0 100644 --- a/text/0000-enum-variant-types.md +++ b/text/0000-enum-variant-types.md @@ -186,8 +186,8 @@ The discriminant of a `Variant` (as observed by [`discriminant_value`](https://d of the variant (i.e. identical to the value observed if the variant is first coerced to the enum type). -Constructors of variants, as well as pattern-matching on particular enum variants, are now -inferred to have variant types, rather than enum types. +Constructors of variants, as well as pattern-matching on particular enum variants, are still +inferred to have enum types, rather than variant types, for backwards compatibility. ```rust enum Sum { @@ -196,9 +196,12 @@ enum Sum { C, } -let x = Sum::A(5); // x: Sum::A +let x: Sum::A = Sum::A(5); // x: Sum::A let Sum::A(y) = x; // ok, y = 5 +let z = Sum::A(5); // x: Sum +let Sum::A(y) = z; // error! + fn sum_match(s: Sum) { match s { a @ Sum::A(_) => { @@ -227,23 +230,9 @@ let x = Sum::A(5) as Sum; // x: Sum let Sum::A(y) = x; // error: refutable match ``` -Inferring the type of an `enum` or variant is now slightly more involved. A new kind of inference -variable, for variants, will be added (akin to numeric type inference). The type for an `enum` or -variant will be chosen according to the following rules: -- If the value is treated as a single variant (and possibly additionally as the `enum`), we choose -the single variant type. For example: -```rust -let x = Sum::A(5); // x: Sum::A -println!("x is {}", x.0); -``` -- If the type is treated as multiple different variants, we choose the `enum` type. -```rust -let mut x = Sum::A(5); // x: Sum -println!("x is {}", x.0); // error: no field `0` on type `Sum` -x = Sum::B; -println!("x is not numeric"); -``` -- In a case where the type variable is unknown, we default to the `enum`. +Inference of the type of an `enum` or variant remains entirely conservative: without explicit type +annotations, a value whose type is an `enum` or variant will always have the `enum` type inferred. +That is to say: variant types are never inferred for values and much instead be explicitly declared. This is backwards-compatible with existing code, as variants act as `enum`s except in cases that were previously invalid (i.e. pattern-matching, where the extra typing information was previously @@ -276,11 +265,11 @@ annotations). The advantages of this approach are: - It naturally allows variants to be treated as types, intuitively. -- It doesn't require explicit type annotations to reap the benefits of variant types. - As variant types and enum types are represented identically, there are no coercion costs. - It doesn't require value-tracking (save the degenerate kind performed by type inference) or complex type system additions such as [refinement types](https://en.wikipedia.org/wiki/Refinement_type). +- It makes no backwards incompatible type inference changes. - Since complete (enum) type information is necessary for variant types, this should be forwards compatible with any extensions to enum types (e.g. [GADTs](https://en.wikipedia.org/wiki/Generalized_algebraic_data_type)). @@ -297,10 +286,10 @@ with the alternative is much more difficult, if possible at all.) Variant types have [previously been proposed for Rust](https://github.com/rust-lang/rfcs/pull/1450). However, it was closed due to uncertainty around the interaction with other forms of type inference -(numeric type inference and default type parameters). Having looked into these, I think adding an -extra kind of type inference for enum variants should not directly interact with these and it is -sensible to open up this RFC without more substantial changes to the proposed implementation method. -Furthermore, the method proposed here is implementationally simpler and more intuitive. +(numeric type inference and default type parameters). The conservative type inference described here +should not directly interact with these and it is sensible to open up this RFC without more +substantial changes to the proposed implementation method. Furthermore, the method proposed here is +implementationally simpler and more intuitive. # Prior art [prior-art]: #prior-art