Live data from Hacker News

State Machines in Rust

blog.yoshuawuyts.com

111–119 of 119 posts

Re: State Machines in Rust

#111

Earlier quoted context omitted.

No Rust, but here's a Python implementation that I have built on top of before: https://github.com/pytransitions/transitions You add the concept of finite "triggers", where [state i] + [trigger result j] always takes you to [new state](which could be the same state if you want) Triggers are just functions where anything could be happening - coin flip, API call, but they return one of an enumerated set of results so t…

Ah ok. I don't write Rust either but maybe it'd look like: impl State { pub fn next(self, Trigger ) -> State { State { _inner: Hibernate {} } } pub fn next(self, Trigger ) -> State { State { _inner: Terminate {} } } }

Usually you would just call your functions hibernate() and terminate(). That way you can call hibernate() on State but not on State or State.

Re: State Machines in Rust

#112
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…

> 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!

I'm surprised no one mentioned this yet, but At has first class support for state charts and state machines in Qt's state machines framework.

https://doc.qt.io/qt-5/statemachine-api.html

Re: State Machines in Rust

#113

In my experience, state machines are very nice in theory, but in practice, over time, they devolve into a mess of spaghetti code. Because they are not in a single scope, loops become the equivalent of a bunch of gotos and managing lifetimes and locks becomes a problem because you can't use scope based mechanisms such as RAII. In Rust, if you want a state machine, generators are probably the long term way to go. https…

I've had exactly the opposite experience -- I've found state machines to be "spaghetti code reducers" -- no longer checking a "bag of flags" to decide how to respond to the event.

For example, the "up" volume button on your phone could mean ringer adjust, MP3 playback volume adjust, take a photo, increase screen brightness, etc. In other words, it depends on what the phone is doing, i.e. its state.

Otherwise, code becomes "if (in_call == true) { adjust_volume(); } else if (camera_mode == true) { take_photo(); } else if ...

Just my 2 cents...

Re: State Machines in Rust

#114

Earlier quoted context omitted.

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.

The braces are redundant because they do not introduce any new information, which is the definition of being redundant.

Whether the resulting grammar is or not LL(2) is orthogonal.

Re: State Machines in Rust

#115

Earlier quoted context omitted.

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 sessi…

You claimed there is a safety issue on "use after free" for empty types which are trivial. I am still waiting to see the proof.

You also keep saying there is no way to fix in C++, you can most definitely make those into compilation errors. And that is if you insist on advancing several states and calling them "stateN" is useful, which I have never done in my life.

Then the last paragraph is another flamebait plus getting into arguments of authority.

Re: State Machines in Rust

#116
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…

In the end they're all situational formalisms. Imperative code is by definition implementing a state machine.

One thing that you can often get by moving the state machine a bit away from the source code as tends to be the case with these libraries is a definition that specifies I/O more concretely. In something like the Unity3D Tick() case, I/O is open-ended: All game state is available, and that state may itself impact when Tick() is called via various concerns of concurrency(timing, update order, etc.). When trying to apply a formal model it's usually a really bad sign to see a very broad "tick" or "update" callback dumped into execution - it leads towards hacky code that sets flags and uses frame boundaries to confirm events.

Game engines don't always have the best examples of these patterns, since games can so often get away with shoddy usage.

Re: State Machines in Rust

#117

Earlier quoted context omitted.

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…

In the end they're all situational formalisms. Imperative code is by definition implementing a state machine. One thing that you can often get by moving the state machine a bit away from the source code as tends to be the case with these libraries is a definition that specifies I/O more concretely. In something like the Unity3D Tick() case, I/O is open-ended: All game state is available, and that state may itself imp…

It’s tricky because the alternative is a bunch of switch statements that just check state == sitToStand or state == FooInProgress every frame, which just moves the logic away from the state itself. Ie, the different behaviours are not just on transitions.

Re: State Machines in Rust

#118

Earlier quoted context omitted.

> 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 sessi…

You claimed there is a safety issue on "use after free" for empty types which are trivial. I am still waiting to see the proof. You also keep saying there is no way to fix in C++, you can most definitely make those into compilation errors. And that is if you insist on advancing several states and calling them "stateN" is useful, which I have never done in my life. Then the last paragraph is another flamebait plus get…

> You claimed there is a safety issue on "use after free" for empty types which are trivial. I am still waiting to see the proof.

I claimed that it is trivial to accidentally introduce safety issues when implementing _state_ machines in C++ like you are proposing. The "state" in "state machines" comes from the machine actually having some state. Naive state machines don't store state, and simple state machines can encode all their state in types, but real world state machines rarely do so (e.g. a regex engine).

> You also keep saying there is no way to fix in C++, you can most definitely make those into compilation errors.

Show how to do that then, e.g., for example for a simple file handle wrapper, that only allows reading a file once:

    struct FileHandle {
      static FileHandle open(const char*);
      ~FileHandle(); // closes file
      struct FileRead { ~FileRead(); /* closes file */ };
      static FileRead read(FileHandle);
    };
such that the file is not closed twice:

   auto file = FileHandle::open("foo");
   auto read = FileHandle::read(file);
   //~ read destructor closes file
   //~ file destructor double-closes file
without doing any run-time checks, e.g., in destructors, that check whether the file is closed.

This is easy peasy in Rust.

Re: State Machines in Rust

#119
post #20

Earlier quoted context omitted.

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!

I think what they're saying is that it literally makes no difference, not negligible difference. At some point you have to do the branching that decides which part of the logic in the state machine runs next. Dynamic dispatch is essentially just a way to do this kind of branching.
Post reply on HN