News for hackers. Stuff that matters.
Re:Using enums to represent state in Rust by diarrhea on Friday September 22, 2023 @10:34AM (#37612608)
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've been a huge fan of the type-state pattern [1]. I don't see it mentioned often, and never outside of Rust so far. However, it's applicable to most languages, including ones you wouldn't suspect (Python). If you introduce a whole new type (not a big deal in Rust; more of a ceremony in C# et al.) for `DeletedUser`, you can simply leave off the `active` function! Any action (==state transition) on that type will be legal and possible. Methods have unit value return type, no `Result` needed. You cannot handle that incorrectly! The code won't even compile.

I am still in the process of exploring downsides to the pattern. For example, in classic OOP languages, you can create a type hierarchy, with a top-level `User`, and the different kinds inheriting from it (and then ideally be marked `final`/`sealed` or whatever). You can then treat all users the same by interacting with the top-level type. Useful for DB interaction, for example. The same in Rust would go through traits: `impl User for DeletedUser`. But it's not quite as nice, is it?

1: https://cliffle.com/blog/rust-typestate/

Re:Using enums to represent state in Rust by hgomersall on Friday September 22, 2023 @10:53AM (#37612843)
It's less good when the state machine traversal is only known at run time. Necessarily you can only capture the errors at run time, so you get less benefit. You can wrap the states in an enum, but then the enum needs to implement the interface of every state. In that pattern though, you do get the benefit that the enum level dispatcher has to properly obey the types' interface, so you get lots of confidence that the runtime errors will be correct (or at least, an error will happen if the state is called incorrectly).

Still, it's a great pattern that I use as often as I can!

Re:Using enums to represent state in Rust by admax88qqq on Friday September 22, 2023 @08:31PM (#37619516)
Sounds like polymorphism with more steps.

An interface with multiple implementations.

Re:Using enums to represent state in Rust by the__alchemist on Friday September 22, 2023 @12:30PM (#37614117)
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 expecting. It can also result in long nests of `<>`.

The typesstate pattern felt hostile when libs using it only included examples of use directly in a main function where you didn't have to specify the type; you'd try to use them in a program in a struct field, function signature etc, and wouldn't know what type to put in.

Example of typestates I've seen: `Spi<SPI1, PA5<Alternate<AF11>>, PA6<Alternate<AF69>...>>>>>>>`

When would be easier to use a plain `Spi` struct.

These aren't necessarily critiques of the typestate pattern in general, but those are the 2 points that pushed me away from it.

Re:Using enums to represent state in Rust by saurik on Friday September 22, 2023 @02:42PM (#37615973)
It sounds like Rust is in dire need of something similar to C++ decltype.
Re:Using enums to represent state in Rust by tialaramex on Friday September 22, 2023 @05:46PM (#37618214)
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 by wging on Friday September 22, 2023 @06:14PM (#37618463)
There’s one thing I can think of that looks like an exception to the lack of interference at function boundaries: you can return impl SomeTrait rather than worrying about the exact thing you’ll return. It’s useful for iterator adapters in particular, where the types depend on the functions you call and in which order, and thus aren’t stable under small modifications to the source code.

(I wouldn’t count impl trait in function parameters since that acts more like a generic type.)

Re:Using enums to represent state in Rust by tialaramex on Saturday September 23, 2023 @08:01AM (#37622493)
That's not inference, you're literally telling callers "the object I'm giving you might have any type but I promise it implements this Trait".

The compiler knows which concrete type it is, but you needn't and your caller isn't promised it is any particular type (but it is, they just aren't allowed to care)

This is useful because all Rust's functions are types, both lambda and ordinary functions are unique types, but we often want to say I'm going to return say a predicate - we can't name the predicate we're going to return but our caller just wants a predicate so they don't care that we couldn't spell its name.

Re:Using enums to represent state in Rust by saurik on Monday September 25, 2023 @09:17PM (#37653086)
If doesn't make a lot of sense in most cases in C++ either (and as auto gets better it has only made sense in fewer cases since it was introduced)... I write a lot of C++ and I essentially never ever ever need or even merely use decltype; but, when it does make sense, it really truly makes sense, and here we have a place in Rust that sounds exactly like the use case in C++ where this actually comes up.
Re:Using enums to represent state in Rust by twic on Friday September 22, 2023 @01:39PM (#37615072)
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...

Re:Using enums to represent state in Rust by myvoiceismypass on Friday September 22, 2023 @03:16PM (#37616417)
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 by tkz1312 on Friday September 22, 2023 @03:58PM (#37616969)
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 by rakenodiax on Saturday September 23, 2023 @10:04AM (#37623324)
Everything that was old is new again. I’m glad that features that were relegated to “esoteric” languages like ML's and lisps are becoming more mainstream. There are many lucky 10k’s! :)
Re:Using enums to represent state in Rust by tkz1312 on Sunday September 24, 2023 @06:29PM (#37637785)
rust mainstreaming a lot of functional concepts and patterns is awesome very much agree, just pushing back on the claim that this it’s the only language where this style is prevalent.
Re:Using enums to represent state in Rust by sfvisser on Friday September 22, 2023 @04:09PM (#37617098)
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.