Live data from Hacker News

Using enums to represent state in Rust

corrode.dev

51–60 of 89 posts

Re: Using enums to represent state in Rust

#51
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…

> 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 done you set it to a type with no members:

    type E₃AndNoMore a b c = a ⊕ b ⊕ c ⊕ Void
I don't know Rust so I can't claim if it allows it, but I'm almost certain it does.

Re: Using enums to represent state in Rust

#52

Earlier quoted context omitted.

I agree that something like 'sum type' sounds a bit too esoteric, but IMHO 'tagged union' is very descriptive. It's the same thing as a C union plus a tag indicating the currently active content (and that's also what the memory layout looks like under the hood). (but yeah, 'enum with arguments' also describes it very well)

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.

Re: Using enums to represent state in Rust

#53
post #46

Earlier quoted context omitted.

That would still be technically misleading, since enums don't necessarily have tags due to niche optimization. Eg `Option` is an enum but `Option >` does not have any tags; it uses the content being zero to represent None since `Box ` cannot be zero. Also, Rust does have actual tagged unions for C interop that you have to define yourself as a `struct` with an int field and a `union` field, just like in C.

Rust enums always have a discriminant: https://doc.rust-lang.org/std/mem/fn.discriminant.html Their representation in memory may not use it, but it's still defined.

They do, but IIRC this mostly to compare enum variants when it can't implement `Eq` and/or `PartialEq`. For example, in tests.

There are also zero variants enums that don't have any discriminant, but still could be used.

I'm not sure what's the point of comparing it to C because enums in C only carry tags and no data, while unions only carry data and no tag. Enums in rust could do both.

Re: Using enums to represent state in Rust

#54

This is good, but could go further if you're pursuing type-system leverage. For example, why does a deleted user have an `activate` associated function in the first place? It will error out! That's safe in Rust and perfectly fine in terms of control flow: it cannot be forgotten, and cannot easily be handled incorrectly (unlike, say, a bool, where a single exclamation mark can mean a nasty bug). But it's not ideal. I'…

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.

Re: Using enums to represent state in Rust

#55

As someone not versed in Rust I understand the point of the article is showing the Enum construct is powerful, but if you are worried about self-documenting code and want to avoid illegal state transitions, isn't it as simple as defining a map of (pseudocode here): {Active -> Inactive, Inactive -> Active, Active -> Suspended, Suspended -> Active, ...} And then having only _one_ function that mutates and checks for th…

To me this feels similar to C++ where you have to choose between static or dynamic dispatch at the implementation (template vs virtual methods). This means the user is forced into it one or the other.

While in Rust, the implementation is done for a Trait, and the user can choose static or dynamic dispatch (Trait vs dyn Trait).

I feel the same dissonance between static and dynamic state machines in Rust (type states vs enum). Sometimes I want to enforce it at compile time, while sometimes, at runtime. And the implementation is forced to choose for the user.

I am sure one could write some (proc) macro, and there might be some crates to do that already. But it doesn't feel as elegant as the static/dynamic Trait in my mind.

Re: Using enums to represent state in Rust

#56
post #45

This is good, but could go further if you're pursuing type-system leverage. For example, why does a deleted user have an `activate` associated function in the first place? It will error out! That's safe in Rust and perfectly fine in terms of control flow: it cannot be forgotten, and cannot easily be handled incorrectly (unlike, say, a bool, where a single exclamation mark can mean a nasty bug). But it's not ideal. I'…

Time to re-pimp my idea of doing this using a single type parameterised to reflect different states: https://github.com/tim-group/higher-kinded-lifecycle/blob/ma...

Seems like more idiomatic scala to have an ADT via a sealed trait hierarchy + pattern matching here.

Re: Using enums to represent state in Rust

#57
post #4

I kind of consider algebraic data types (the combination of this style of enum (sum types) and structs (product types)) paired with pattern matching as the core feature set that any modern language must support. It makes for really simple, easy to follow code that’s much less error prone than the alternatives.

I just wish Rust wouldn't have called those things 'enums' but 'tagged unions', would have saved us C peasants a lot of confusion when encountering them first ;)

Discriminated Records is The Way.

Re: Using enums to represent state in Rust

#58

This is good, but could go further if you're pursuing type-system leverage. For example, why does a deleted user have an `activate` associated function in the first place? It will error out! That's safe in Rust and perfectly fine in terms of control flow: it cannot be forgotten, and cannot easily be handled incorrectly (unlike, say, a bool, where a single exclamation mark can mean a nasty bug). But it's not ideal. I'…

This kind of thing has been very standard in other ML family languages for decades now. Rust is a nice language, but it’s frustrating when its proponents act as if it’s the first language to have these kind of features. ML is 50 years old at this point…

Re: Using enums to represent state in Rust

#59

This is good, but could go further if you're pursuing type-system leverage. For example, why does a deleted user have an `activate` associated function in the first place? It will error out! That's safe in Rust and perfectly fine in terms of control flow: it cannot be forgotten, and cannot easily be handled incorrectly (unlike, say, a bool, where a single exclamation mark can mean a nasty bug). But it's not ideal. I'…

If you like this kind of pattern you might want to read up on Haskell’s GADTs. Generalized algebraic data-types.

This is where you can index the different constructors (enum variants) with an additional type variables and even specialize them when needed. A pretty powerful tool to encode at the type level that similar things are slightly different.

Re: Using enums to represent state in Rust

#60
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…

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

Post reply on HN