question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Enum Variant Types

See original GitHub issue

Proposal

Summary and problem statement

(taken from: https://github.com/rust-lang/rfcs/pull/2593)

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.

enum Either<A, B> { L(A), R(B) }

fn all_right<A, B>(b: B) -> Either<A, B>::R {
    Either::R(b)
}

let Either::R(b) = all_right::<(), _>(1729);
println!("b = {}", b);

Motivation, use-cases, and solution sketches

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 (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.

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

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 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 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 you may irrefutably pattern-match on a variant:
enum Sum { A(u32), B, C }

fn print_A(a: Sum::A) {
    let A(x) = a;
    println!("a is {}", x);
}

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:

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
}

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:
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 (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.
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:

enum Sum { A(u32), B, C }

type SumA = Sum::A;
// `SumA` may now be used identically to `Sum::A`.

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 or cast to variant types. Instead, matching must be performed to guarantee that the enum type truly is of the expected variant type.

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 {
    // ok, `a` has type `Sum::A`
    println!("a is {}", a.0);
}

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 (i.e. the same as type coercion in let or similar).

Type parameters

Consider the following enum:

enum Either<A, B> {
    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 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. 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<A, B>, Either<A, B>::L and Either::<A, B>::R.

Links and related work

  1. https://github.com/rust-lang/rust/pull/89745 (draft PR)

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:23
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

27reactions
nikomatsakiscommented, Nov 9, 2021

Hi! We discussed this in a number of recent @rust-lang/lang meetings (2021-11-02, 2021-11-03). Based on those meetings, we decided that we are going to close this issue, but not without a good helping of regret.

The bottom line is this: we all agree that it is a common, and annoying, pattern in Rust today to have to make a struct for every enum variant and then just have the enum wrap those structs. This gives you the ability to have a “type for an enum variant”, but is annoying and inconvenient. So, for those reasons, we would love to see forward motion on this proposal.

However, we also feel that this is striking at a fairly core part of the language, and there isn’t anyone on the team who has the bandwidth to actively liaison this effort. We’ve had the experience (cough never type cough) of accepting fundamental extensions to the language without dedicating real bandwidth to implementing and supporting them, and it is not good. So we are trying to do better on that score.

One other concern that was raised is that there are a lot of “nice to haves” here – for example, it might be nice if None didn’t have a type parameter – but they can’t all be realized (e.g., None needs a size). We’re not sure whether just taking a minimalist approach of adding a type for each enum variant, but not supporting e.g. sets of variants, GADTs, or other niceties, will land up in a place that is better than the status quo or more confusing (at least the status quo is relatively simple). Leaning on coercions and the like has a tendency to introduce sharp edges, and I am definitely concerned that we get this feature to a point where “95% of existing code” works fine but there is some stubborn cohort that doesn’t, and it becomes an additional axis of complexity for folks to navigate.

One possibility might be using a procedural macro to try and prototype some of the ideas, though I haven’t invested much thought into how that might work. I’m going to tag this as “final comment period”, feel free to respond to any of the points above; the team can discuss again next week and decide whether or not to truly close.

10reactions
MatrixDevcommented, Nov 9, 2021

We’re not sure whether just taking a minimalist approach of adding a type for each enum variant,

This is a good one and I’d really love to see it implemented.

PS: just don’t add more “matches!”-like macros for enums. IMHO it really looks like a half-baked solution/workaround.

Read more comments on GitHub >

github_iconTop Results From Across the Web

enum_variant_type - Rust - Docs.rs
Proc macro derive to generate structs from enum variants. This is a poor-man's implementation of https://github.com/rust-lang/rfcs/pull/2593. [dependencies] ...
Read more >
Enum Variants as types : r/rust - Reddit
Enum Variants as types. Is it possible through current Unstable language features, maybe const generics or something else ...
Read more >
enum - Rust
A type that can be any one of several variants. Enums in Rust are similar to those of other compiled languages like C,...
Read more >
More Enum Types - Yoshua Wuyts
In an enum, only one variant is active at any given time. So let's define an enum where we either have a u32...
Read more >
2338-type-alias-enum-variants - The Rust RFC Book
This RFC proposes to allow access to enum variants through type aliases. This enables better abstraction/information hiding by encapsulating enums in ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found