Crate statig

source ·
Expand description

statig

Hierarchical state machines for designing event-driven systems.

Features

  • Hierachically nested states
  • State-local storage
  • Compatible with #![no_std], no dynamic memory allocation
  • (Optional) macro’s for reducing boilerplate.

statig in action

#[derive(Default)]
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();
}

#[state_machine]
impl Blinky {
    #[state]
    fn on(&mut self, event: &Event) -> Response<State> {
        self.led = false;
        Transition(State::off())
    }

    #[state]
    fn off(&mut self, event: &Event) -> Response<State> {
        self.led = true;
        Transition(State::on())
    }
}

let mut state_machine = Blinky::default().state_machine().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).

Concepts

States

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.

#[state]
fn on(event: &Event) -> Response<State> {
    Transition(State::off())
}

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

Superstates allow you to create a hierarchy of states. States can defer an event to their superstate by returning the Super response.

#[state(superstate = "playing")]
fn on(event: &Event) -> Response<State> {
    match event {
        Event::TimerElapsed => Transition(State::off()),
        Event::ButtonPressed => Super
    }
}

#[superstate]
fn playing(event: &Event) -> Response<State> {
    match event {
        Event::ButtonPressed => Transition(State::paused()),
        _ => Handled
    }
}

Superstates can themselves also have superstates.

Actions

Actions run when entering or leaving states during a transition.

#[state(entry_action = "enter_on", exit_action = "exit_on")]
fn on(event: &Event) -> Response<State> {
    Transition(State::off())
}

#[action]
fn enter_on() {
    println!("Entered on");
}

#[action]
fn exit_on() {
    println!("Exited on");
}

Context

If the type on which your state machine is implemented has any fields, you can access them inside all states, superstates or actions.

#[state]
fn on(&mut self, event: &Event) -> Response<State> {
    self.led = false;
    Transition(State::off())
}

Or alternatively, set led inside the entry action.

#[action]
fn enter_off(&mut self) {
    self.led = false;
}

State-local storage

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.

#[state]
fn on(counter: &mut u32, event: &Event) -> Response<State> {
    match event {
        Event::TimerElapsed => {
            *counter -= 1;
            if *counter == 0 {
                Transition(State::off())
            } else {
                Handled
            }
        }
        Event::ButtonPressed => Transition(State::on(10))
    }
}

counter is only available in the on state but can also be accessed in its superstates and actions.

FAQ

What is this #[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.

Credits

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.

Modules

Structs

A state machine that has been initialized.
A state machine that has not yet been initialized.

Enums

Response that can be returned by a state machine.

Traits

An enum that represents the leaf states of the state machine.
Extensions for State trait.
A data structure that declares the types associated with the state machine.
A state machine where the context is of type Self.
A state machine where the context is not of type Self.
An enum that represents the superstates of the state machine.
Extensions for Superstate trait.

Attribute Macros

Attribute for tagging an action.
Attribute for tagging a state.
Macro for deriving the state and superstate enum.
Attribute for tagging a superstate.