Sequential state machines

Let's first think about how to implement this in a C/C++ environment. You will probably have a global state and then a while loop that would change the state after each iteration. There are, of course, many ways of implementing a state machine but, in C, all of them require either metaprogramming or a runtime evaluation of the global state.

In Rust, we have a Turing-complete type system, so why not try and use it to create that state machine? Let's start by defining some traits that will have the power of creating the state machine. We first define a StateMachine trait, that will have the functionality of moving from one state to another state:

pub trait StateMachine {
type Next: MainLogic;
fn execute(self) -> Self::Next;
}

As you can see, I already added a new type, MainLogic. This will be a trait representing a structure that can perform a logic in a state. The StateMachine trait itself is pretty simple. It only contains a type that will be the next state and an execute() function that consumes itself so that nobody can execute the same state twice without going to the next state (the next state could be itself again). It simply returns a new state machine. Here we have the MainLogic trait:

pub trait MainLogic {
fn main_logic(self);
}

It's just a function to execute the logic of the state. The main functionality of this state machine, that will enable it to go from one state to the next, always doing the proper logic, is defined in the default implementation of the MainLogic trait:

impl<S> MainLogic for S
where
S: StateMachine,
{
fn main_logic(self) {
self.execute().main_logic();
}
}

This will implement the MainLogic trait for any state implementing the StateMachine trait. It will simply execute the state and then call the main logic of the next state. If this new state is also a StateMachine, it will get executed and then the next state will be executed. This pattern is especially useful if you want to sequentially execute different states. The last state will be the one implementing MainLogic but not StateMachine:

struct FirstState;
struct LastState;

impl StateMachine for FirstState {
type Next = LastState;

fn execute(self) -> Self::Next {
unimplemented!()
}
}

impl MainLogic for LastState {
fn main_logic(self) {
unimplemented!()
}
}

The compiler will make sure at compile time that you properly go from the first state to the second one, and it will force you to do so. But, more importantly, this will be compiled into very efficient code, as efficient as doing the sequential calls one by one, but with all the safety Rust gives you. In fact, as you can see, both FirstState and Laststate have no attributes. That is because they have no size. They will not occupy space in memory at runtime.

This is the simplest state machine though. It will only allow you to advance from one state to the next. It's helpful if that's what you want, since it will make sure your flow gets checked at compile time, but it will not perform complex patterns. If you loop over a previous state, you will endlessly continue looping. This will also be useful when each state has a defined next state and when no other possibility comes from that state.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.106.7