Live data from Hacker News

State Machines in Rust

blog.yoshuawuyts.com

101–110 of 119 posts

Re: State Machines in Rust

#101

Noob question but what about state machines where a given state could transition to more than one other state depending on some outside factors? Or is that no longer considered a state machine? For a relevant to me example, a VM state. A VM in running state could be transitioned to terminated or stopped or hibernating depending on an admins action.

[deleted]

Re: State Machines in Rust

#102
post #59

Earlier quoted context omitted.

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 and TryFrom implementations that enable the specific transitions…

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…

I've had good luck in the past using From and TryFrom to go from State -> State. 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.

Re: State Machines in Rust

#103
post #28

For you who enjoying using state machines but wish they did even more and/or were embedded in each other (nested state machines!), check out this thing called State Charts! Here is the initial paper from David Harel: STATECHARTS: A VISUAL FORMALISM FOR COMPLEX SYSTEMS (1987) - https://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/res... Website with lots of info and resources: https://statecharts.github.io/ And fi…

What exactly do the libraries provide? When I use state machines, if it’s small, a switch is all you need; and if large, the State pattern is great. Ie, what am I missing out on?

The main challenge for me is when a given state comes with a “Tick” method that gets invoked via current_state.Tick() - Eg, in an update method in Unity. This makes them a tiny bit more hairy to work with, and I am not certain what the best practice is to keep this very simple.

I know bob martin has a video course on creating a custom compiler to generate state machines from tables. That seems neat too

Re: State Machines in Rust

#105
post #20

Earlier quoted context omitted.

Of course dynamic dispatch helps, but using it for a state machine is going to raise eyebrows and kill performance.

I think this would depend on the application. Its possibly a problem in a parser of large data, but in a network protocol or business rule its not going make any difference at all.

If you are using a compiled, systems programming language like Rust, you do care about performance to begin with.

Specially in things like parsing or a network protocol!

Re: State Machines in Rust

#106
post #28

For you who enjoying using state machines but wish they did even more and/or were embedded in each other (nested state machines!), check out this thing called State Charts! Here is the initial paper from David Harel: STATECHARTS: A VISUAL FORMALISM FOR COMPLEX SYSTEMS (1987) - https://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/res... Website with lots of info and resources: https://statecharts.github.io/ And fi…

To these links I would also add Miro Samek's book "Practical UML Statecharts in C/C++," which covers details of implementing (hierarchical) statecharts. The book includes a chapter that analyses different ways of encoding extended state machines (Miro concludes a function pointer, with one event handler function per state is the best for C/C++. I don't think this is the encoding that the linked Rust article uses). A simple algorithm for implementing HSMs is described. A large portion of the book is devoted to what you might call "UML Statecharts -- The Good Parts," the parts of the UML statecharts spec that are easy to implement in a real-time setting, and idioms/patterns to achieve similar results as the parts of the spec that are not easy to implement.

Some of Miro's online writings:

https://barrgroup.com/embedded-systems/how-to/state-machines... https://barrgroup.com/embedded-systems/how-to/introduction-h...

https://www.drdobbs.com/who-moved-my-state/184401643

[Edit: clarity]

Re: State Machines in Rust

#107
post #81
post #60

Earlier quoted context omitted.

that's known as an NFA (Nondeterministic Finite Automaton), a variant of FSMs.

Nondeterminism not needed (or desired I think :D) for an FSM that can turn a VM on or off based on start/stop buttons. Its just multiple possible transitions, guarded by different conditions (the buttons). But yeah, Nondeterministic FSMs are possible. Ie based on a transition probability.

the "nondeterminism" here doesn't mean we're dealing with probabilities, it's a more discrete kind - it just means that instead of

  S1 --a--> S2
you can have

  S1 --a--> S2
    '--a--> S3
    '--a--> S4
i.e. transition to multiple states "at once"¹. then, instead of being in one state, like an FSM, your NFA is in a set of states, like it had multiple threads, and proceeds "in parallel" from each state. probably not the best explanation, but i'm sure you can find good material about this.

---

¹ this a way to represent nondeterminism in a pure/math-y setting: instead of

  def f():
    b = random_bool()
    if b:
      res = "yes"
    else:
      res = "no"
    return res
you do

  def random_bool2():
    return {True, False}

  def f2():
    res = set()
    for b in random_bool2():
      if b:
        res.add("yes")
      else:
        res.add("no")
    return res
or just:

  def f2():
    return {"yes", "no"}
i.e enumerate all the possible results f() could give depending on what `random_bool()` returns.

Re: State Machines in Rust

#108
post #28

For you who enjoying using state machines but wish they did even more and/or were embedded in each other (nested state machines!), check out this thing called State Charts! Here is the initial paper from David Harel: STATECHARTS: A VISUAL FORMALISM FOR COMPLEX SYSTEMS (1987) - https://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/res... Website with lots of info and resources: https://statecharts.github.io/ And fi…

I just saw this page https://statecharts.github.io/faq/an-event-always-has-one-ac...

>Because if you think harder about it, an event will usually have some deviation from doing that action. Most buttons are clickable, and when you click it, then you perform the action. However, some users doubleclick most buttons, and in those cases, rarely do you want to repeat that action.

This type of event duplication has been the source of glitches in pretty much every html5 based game. What happens is that there is a dialogue with an onclick handler and triggering the handler causes the dialog to close with an animation. If you click the dialogue again before it closes it will execute the onclick handler again. This lets you duplicate quest rewards in a lot of RPGs.

Re: State Machines in Rust

#109

Earlier quoted context omitted.

C++ requires using the template keyword to disambiguate, i.e., one has to write `foo.template bar 2>()` as opposed to `foo.bar 2>()` (did you mean `(foo.bar ()` ?). The main reason Rust meta-programming is so much better than D and C++ is that Rust has an LL(k) grammar that's trivial to parse into ASTs that can be easily manipulated. That feature alone is definitely worth the annoyance of having to use an editor that…

Removing redundancy is not always a good idea, but the braces are redundant.

Can you elaborate? If you remove the braces, you break one of Rust's most important language features, an LL(2) grammar.

That is, those braces give Rust an LL(2) grammar, and therefore, are not redundant.

If you know how to preserve the LL(2) grammar in Rust while removing those braces, please explain why, since the answer would revolutionize many fields of computer science.

Re: State Machines in Rust

#110

Earlier quoted context omitted.

That example does not work, because declaring `state` multiple times creates an illegal C++ program (redeclaration of local variable - notice that this is not the case in Rust). You need to declare variables with different names: const auto state0 = newState(); const auto state1 = next(state0); const auto state2 = next(state1); const auto state3 = next(state); // TYPO -> BOOM use after move I don't think this can be…

Nobody is using state machines to advance several times through the states with variables named "stateN", so I am not sure what is the point. There is no "BOOM" either since "use after move" is not a safety concern for those empty types, just a logic bug, which will likely appear at compile-time since your template specialization would not match your expectations. The redeclaration in Rust always makes me uneasy as a…

> Nobody is using state machines to advance several times through the states with variables named "stateN", so I am not sure what is the point.

The point is that in C++ every time you advance the state you "split" the state machine into two - one that can be used by mistake and doing so introduces a bug, and one that is the one that should be used.

In programming languages that proper support state machines (or session types, or any similar pattern), that split is guaranteed to be impossible, so you get the guarantee that users cannot misuse your API, because attempting to do so is a guaranteed compilation error.

> There is no "BOOM" either since "use after move" is not a safety concern for those empty types, just a logic bug, which will likely appear at compile-time since your template specialization would not match your expectations.

This isn't true: even if `state0` and `state1` have different types, as you are proposing, your proposed `next` function accepts both types according to your design without a compilation error.

There is "no" fix for this in C++. Even if you were to introduce `next0`, `next1`, etc. that only accepts one type, the one of `state0`, `state1`, etc. that would create a compilation error here:

    auto state0 = next(initial_state);
    auto state1 = next0(state0); 
    auto state2 = next0(state1); // ct-error: use next1(state1)
but the underlying error is still there, and that is that the user can write

    auto state2 = next0(state0); // use-after-move
that's a logic error that Rust catches at compile-time, but C++ would need to catch at run-time, and catching this at run-time adds overhead, since now you need to store in some run-time data-structure in which state the state machine is, to be able to verify these things (while in Rust, you don't have to track this at run-time at all).

> There is no "BOOM" either since "use after move" is not a safety concern for those empty types, just a logic bug

Rust allows you to assume that this logic bug never happens. C++ code that assumes this can easily have undefined behavior due to the logic bug happening. That is, C++ code cannot assume that the state machine will only go from one state to the next, at least, without the whole state machine library / implementation checking at every step that these bugs do not happen, and, e.g., terminating the program if that's the case That's a valid solution, and probably the best solution that can be implemented in C++, but compared to what Rust and other languages offer, it is a very bad solution and the consequences are quite drastic (state machines, session types, etc. are widely used in Rust to design APIs, while they aren't really used in C++ because they are very boiler plate heavy, complex to implement, and incur a lot of runtime overhead to prevent these errors).

> The redeclaration in Rust always makes me uneasy as a default. It would have been better to require special syntax.

How many years have you been a full-time Rust user ? Or how many of your C++ projects use the "state machine C++ pattern" that you are advocating here ? How many developers are involved in each of those projects ?

Post reply on HN