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.
State Machines in Rust
101–110 of 119 posts
Re: State Machines in Rust
#102Earlier 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…
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
#103For 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…
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
#104Re: State Machines in Rust
#105Earlier 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.
Specially in things like parsing or a network protocol!
Re: State Machines in Rust
#106For 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…
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
#107Earlier 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.
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
#108For 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…
>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
#109Earlier 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.
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
#110Earlier 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…
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 ?