Hacker News new | ask | show | jobs
by unlinked_dll 2264 days ago
I disagree with the implementation, State should be a trait with NextState as an associated type. This makes things cumbersome when it can be a set of types, but it makes excellent use of the type system and ownership patterns of Rust. Edit: and the type state pattern http://cliffle.com/blog/rust-typestate/

As an aside, if you want to dive in with FSMs and automata theory (as well as some basic language topics) go read Sipser's book, "Introduction to the Theory of Computation." You'll go from logic to state machines to understanding why Turing Machines are so dope in a few dozen pages. The math isn't bad.

2 comments

the article includes a section on the state as a generic type parameter, though?

in general, state as a type parameter is useful when there's some data that you want for every state (say, unique id, time of event), so those can be normal fields on the State struct, and then each event type can hold event-specific data. You can tie it together with From<T> and TryFrom<T> implementations that enable the specific transitions you want to allow.

Associated types are slightly different than generic types.

    trait State {
        type NextState;
        fn transition(self) -> Self::NextState;
     } 
In this example one consumes the current state to yield the next state (one could use From/TryFrom, but I don't think that makes sense semantically, imo).

The advantage of this approach is that if you design your State types such that they can only be constructed by transitioning from a previous state, you cannot write code where your program is accidentally in an invalid state. You also never need to match against an enum.

I've had good luck in the past using From<T> and TryFrom<T> to go from State<A> -> State<B>. It's similar to what you propose insofar as there are only some transitions defined, just relying on the built-in traits instead of using your own. Since State::from(prev) is so much more ergonomic than the other ways you could manually construct an illegal transition, it's not much of a problem to prevent that in practice. But I would never use this approach without being able to generate code with macros, otherwise it'd be a pain.

you mention "never have to match against an enum" - I think that's the biggest thing I learned from my experiences, is that generics or traits + associated types are much nicer to use than enums for the same purposes. With an enum, you have to match to pull any data out. It becomes annoying quickly.

You should keep going with this and see how ergonomic it is when NextState can be one of many states. I tried implementing State Machines once with this method and ended up wanting to use an `enum` for `State::NextState`. After a while it ended up being pretty unwieldy.
See Nemo157's response to cerebellum42. He explains why having this in runtime is not the best approach for FSMs.
Nothing I suggested implies a runtime penalty. Quite the opposite actually.