3 ms·
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 f
by diarrhea 3y ago
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/ https://cliffle.com/blog/rust-typestate/
- hgomersall 3y agoIt'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' interfaces, 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!
- admax88qqq 3y agoSounds like polymorphism with more steps. An interface with multiple implementations.
- the__alchemist 3y agoSome 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.
- saurik 3y agoIt sounds like Rust is in dire need of something similar to C++ decltype.
- tialaramex 3y agoThis 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.
- saurik 3y agoIf 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.
- twic 3y agoTime 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/master/src/main/scala/Idea.scala https://github.com/tim-group/higher-kinded-lifecycle/blob/ma...
- myvoiceismypass 3y agoSeems like more idiomatic scala to have an ADT via a sealed trait hierarchy + pattern matching here.
- tkz1312 3y agoThis 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…
- rakenodiax 3y agoEverything 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! :)
- tkz1312 3y agorust 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.
- sfvisser 3y agoIf 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.