Live data from Hacker News

Using enums to represent state in Rust

corrode.dev

61–70 of 89 posts

Re: Using enums to represent state in Rust

#61
post #48
post #26

Enums are fantastic. But they could be better! One thing Rust could really use are anonymous unions (A | B |C instead of E::A(A), E::B(B), E::C(C)). They are to enums what tuple types are to structs. Another thing that a new language designer might consider is a mechanism to control the layout. For example say I have a pair of nested enums enum A { A0(B), ... A15(B), } enum B { B0, ... B15, } The outer enum A can be…

> One thing Rust could really use are anonymous unions (A | B |C instead of E::A(A), E::B(B), E::C(C)). They are to enums what tuple types are to structs. Ceylon had union types, which is the only place i've seen these: https://github.com/eclipse-archived/ceylon-lang.org/blob/mas... Another thing Rust enums are missing is having each variant be a type. If you have an enum Shape with variants Circle, Rectangle, and Po…

Typescript and Dart have union types that work this way.

Re: Using enums to represent state in Rust

#62
post #52

Earlier quoted context omitted.

They do point them out as tagged unions many times in the documentation. But the syntax `tagged_union Whatever {}` doesn't exactly have me jumping out of my seat.

It’s not like “enum” and “mut” are complete English words either, so maybe an abbreviation like “tun” could have worked. But honestly I think enum was a better choice.

I personally think enum was basically the only reasonable choice, as normal enums really are just a subset of tagged unions. They just happen to support tagged unions on top of them.

Re: Using enums to represent state in Rust

#63
post #60

Earlier quoted context omitted.

> They are to enums what tuple types are to structs. But this is just a generic sum type? data Sum a b = L a | R b infixr 5 type Sum as ⊕ type E₂ a b z = a ⊕ b ⊕ z type E₃ a b c z = a ⊕ b ⊕ c ⊕ z -- and so on… Here, `Eₙ` represents a sum type with at least `n` members indexed by their position, and `z` represents any type so that it's possible to keep extending the number of positions via further nesting. When you're…

> But this is just a generic sum type? No, it's actually less generic. It's not determined by position but by type. For example `A | B | A` is the same type as `A | B`. This is useful as a shorthand when you don't want/need a new type to represent your problem, similar to tuples.

So you're talking about untagged unions?

> This is useful as a shorthand when you don't want/need a new type to represent your problem, similar to tuples.

Yes this is handled perfectly by the generic sum type, you don't need untagged unions for this. Rust used to have Either in its standard library, but they removed it and kept Result only. Semantically they're the same (a ⊕ b) but Result's name implies it has something to do with some "results". Anyways nothing stops you from creating one yourself, or even using Result if you're fine with the weird-sounding name.

Re: Using enums to represent state in Rust

#64
post #60

Earlier quoted context omitted.

> But this is just a generic sum type? No, it's actually less generic. It's not determined by position but by type. For example `A | B | A` is the same type as `A | B`. This is useful as a shorthand when you don't want/need a new type to represent your problem, similar to tuples.

So you're talking about untagged unions? > This is useful as a shorthand when you don't want/need a new type to represent your problem, similar to tuples. Yes this is handled perfectly by the generic sum type, you don't need untagged unions for this. Rust used to have Either in its standard library, but they removed it and kept Result only. Semantically they're the same (a ⊕ b) but Result's name implies it has someth…

No, I am not talking about untagged unions. I don't understand your notation. To be concrete, I am talking about tagged, disjoint union type, that does not require naming a new type to use.

This is also not covered by the Either/Result type.

Rust supports untagged unions, but they cannot be matched (because they have no tag). An anonymous union would still be tagged internally, but would be less general purpose than the generic enum type.

Re: Using enums to represent state in Rust

#65
post #64

Earlier quoted context omitted.

So you're talking about untagged unions? > This is useful as a shorthand when you don't want/need a new type to represent your problem, similar to tuples. Yes this is handled perfectly by the generic sum type, you don't need untagged unions for this. Rust used to have Either in its standard library, but they removed it and kept Result only. Semantically they're the same (a ⊕ b) but Result's name implies it has someth…

No, I am not talking about untagged unions. I don't understand your notation. To be concrete, I am talking about tagged, disjoint union type, that does not require naming a new type to use. This is also not covered by the Either/Result type. Rust supports untagged unions, but they cannot be matched (because they have no tag). An anonymous union would still be tagged internally, but would be less general purpose than…

> To be concrete, I am talking about tagged, disjoint union type

But you just said "For example `A | B | A` is the same type as `A | B`". How would this be possible for tagged union types?

> that does not require naming a new type to use

> This is also not covered by the Either/Result type

It's more probable that I'm just not understanding what you're talking about, but *the only* re-usable tagged union type similar to tuples is *the* sum type.

Let's say you're dealing coffee. People want it either with sugar or without sugar. You don't want to create a new sum type CoffeeFlavor? Fine, just use Either. This is *the* equivalent of a tuple. You need more than 2 options? No problem, Either>. I don't know what else could be a "anonymous tagged union".

Re: Using enums to represent state in Rust

#66
post #64

Earlier quoted context omitted.

No, I am not talking about untagged unions. I don't understand your notation. To be concrete, I am talking about tagged, disjoint union type, that does not require naming a new type to use. This is also not covered by the Either/Result type. Rust supports untagged unions, but they cannot be matched (because they have no tag). An anonymous union would still be tagged internally, but would be less general purpose than…

> To be concrete, I am talking about tagged, disjoint union type But you just said "For example `A | B | A` is the same type as `A | B`". How would this be possible for tagged union types? > that does not require naming a new type to use > This is also not covered by the Either/Result type It's more probable that I'm just not understanding what you're talking about, but *the only* re-usable tagged union type similar…

Ah ok I think we're mixing up terms here - in the context of systems programming languages, a "tagged" union refers to an integer in front of a bag of bytes that holds the data of the "un tagged" union. Rust has both tagged (enums) and untagged (unions) union types.

What you're asking about is a discriminated vs non-discriminated union, and indeed, that's exactly what I'm talking about.

A | B |C is not the same type as Either> because Either> cannot type check as Either.

But even if you want to argue that you can represent things that way, it misses the point. The goal is to remove complexity from the type hierarchy of the program, not add to it.

Re: Using enums to represent state in Rust

#67
post #66

Earlier quoted context omitted.

> To be concrete, I am talking about tagged, disjoint union type But you just said "For example `A | B | A` is the same type as `A | B`". How would this be possible for tagged union types? > that does not require naming a new type to use > This is also not covered by the Either/Result type It's more probable that I'm just not understanding what you're talking about, but *the only* re-usable tagged union type similar…

Ah ok I think we're mixing up terms here - in the context of systems programming languages, a "tagged" union refers to an integer in front of a bag of bytes that holds the data of the "un tagged" union. Rust has both tagged (enums) and untagged (unions) union types. What you're asking about is a discriminated vs non-discriminated union, and indeed, that's exactly what I'm talking about. A | B |C is not the same type…

> because Either> cannot type check as Either

Why would you want the former to type check as the latter? Where do you see the complexity?

Re: Using enums to represent state in Rust

#68
post #48
post #26

Enums are fantastic. But they could be better! One thing Rust could really use are anonymous unions (A | B |C instead of E::A(A), E::B(B), E::C(C)). They are to enums what tuple types are to structs. Another thing that a new language designer might consider is a mechanism to control the layout. For example say I have a pair of nested enums enum A { A0(B), ... A15(B), } enum B { B0, ... B15, } The outer enum A can be…

> One thing Rust could really use are anonymous unions (A | B |C instead of E::A(A), E::B(B), E::C(C)). They are to enums what tuple types are to structs. Ceylon had union types, which is the only place i've seen these: https://github.com/eclipse-archived/ceylon-lang.org/blob/mas... Another thing Rust enums are missing is having each variant be a type. If you have an enum Shape with variants Circle, Rectangle, and Po…

Ceylon had a really neat type system - sadly it didn't take off. However before that these types in particular were a feature of OCaml: https://v2.ocaml.org/manual/polyvariant.html There they're called Polymorphic Variants. Note also the implementation efficiency concerns on that page - given that Rust is a systems language, defaulting to the current sum types instead of set-theoretic unions was a reasonable choice. Having the option to use them natively and without macros (there's some crates) would be nice still when aware of the additional performance overhead.

Re: Using enums to represent state in Rust

#69
post #54

Earlier quoted context omitted.

Some of the earlier Rust embedded UIs demonstrate downsides. I like the idea in principle (Letting the compiler catch misconfiguration), but in the implementations I've seen, they don't work well with Rust docs, ie figuring out what types to declare, or how to construct things. My workaround was inserting arbitrary types like `i8` in relevant places and seeing what compiler message is output for the type it was expec…

It sounds like Rust is in dire need of something similar to C++ decltype.

This feature wouldn't make a lot of sense in most cases in Rust. Rust has full type interference inside functions, so if we're inside the function body we can allow the type to be inferred, partially or entirely. For example Vec says this is a Vec of something but we're not specifying what it's a Vec of.

In the function signature, Rust deliberately doesn't have inference, you must write down the types and decltype would not be acceptable for that purpose.

Re: Using enums to represent state in Rust

#70
post #66

Earlier quoted context omitted.

Ah ok I think we're mixing up terms here - in the context of systems programming languages, a "tagged" union refers to an integer in front of a bag of bytes that holds the data of the "un tagged" union. Rust has both tagged (enums) and untagged (unions) union types. What you're asking about is a discriminated vs non-discriminated union, and indeed, that's exactly what I'm talking about. A | B |C is not the same type…

> because Either > cannot type check as Either Why would you want the former to type check as the latter? Where do you see the complexity?

Because your code might not need to care about the position you insert your A or B (left/right for Either), you also might not care whether it's an (encoding as) Either or SomeoneElsesEither and you also don't want to have to deal with flattening nested Either's as in the example.

These types are also called "set-theoretic" types as A|B means exactly the set of all values that can be typed as A or typed as B - note that this also induces a whole subtyping rule by set inclusion and this is in contrast to sum types where a value typed Either can never be typed A or B - to move between them you need to apply extractors/match/constructors/(not sure of standard type theory nomenclature).

Implementation of these union types might still need additional tags and construction/matching/extraction underneath, but from a programming perspective there's less complexity as compared to involving an additional named type Either (or EitherOf3 and EitherOf4 and ...) and manually implementing set-theoretic laws.

Post reply on HN