Hierarchical state machines for designing event-driven systems.
Features
#![no_std]
, no dynamic memory allocation```rust
pub struct Blinky { led: bool, }
pub struct Event;
impl StateMachine for Blinky { type State = State;
type Superstate<'a> = Superstate;
type Event = Event;
type Context = Self;
const INIT_STATE: State = State::off();
}
impl Blinky {
#[state]
fn on(&mut self, event: &Event) -> Response
#[state]
fn off(&mut self, event: &Event) -> Response<State> {
self.led = true;
Transition(State::on())
}
}
fn main() { let mut statemachine = Blinky::default().statemachine().init();
state_machine.handle(&Event);
} ```
(See the macro/basic
example for the full code with comments. Or see no_macro/basic
for a version without using macro's).
States are defined by writing methods inside the impl
block and adding the #[state]
attribute to them. By default the event
argument will map to the event handled by the state machine.
```rust
fn on(event: &Event) -> Response
Every state must return a Response
. A Response
can be one of three things:
Handled
: The event has been handled.Transition
: Transition to another state.Super
: Defer the event to the next superstate.Superstates allow you to create a hierarchy of states. States can defer an event to their superstate by returning the Super
response.
```rust
fn on(event: &Event) -> Response
fn playing(event: &Event) -> Response
Superstates can themselves also have superstates.
Actions run when entering or leaving states during a transition.
```rust
fn on(event: &Event) -> Response
fn enter_on() { println!("Entered on"); }
fn exit_on() { println!("Exited on"); } ```
If the type on which your state machine is implemented has any fields, you can access them inside all states, superstates or actions.
```rust
fn on(&mut self, event: &Event) -> Response
Or alternatively, set led
inside the entry action.
```rust
fn enter_off(&mut self) { self.led = false; } ```
Sometimes you have data that only exists in a certain state. Instead of adding this data to the context and potentially having to unwrap an Option<T>
, you can add it as an input to your state handler.
```rust
fn on(counter: &mut u32, event: &Event) -> Response
counter
is only available in the on
state but can also be accessed in its superstates and actions.
#[state_machine]
proc-macro doing to my code? 🤨Short answer: nothing. #[state_machine]
simply parses the underlying impl
block and derives some code based on its content and adds it to your source file. Your code will still be there, unchanged. In fact #[state_machine]
could have been a derive macro, but at the moment Rust only allows derive macros to be used on enums and structs. If you'd like to see what the generated code looks like take a look at the test with and without macros.
{insert uml feature}
?The idea for this library came from reading the book Practical UML Statecharts in C/C++. I highly recommend it if you want to learn how to use state machines to design complex systems.