statig_macro 0.2.0

Macro's for `statig`
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# statig

[![Current crates.io version](https://img.shields.io/crates/v/statig.svg)](https://crates.io/crates/statig)
[![Documentation](https://docs.rs/statig/badge.svg)](https://docs.rs/statig)
[![Rust version](https://img.shields.io/badge/Rust-1.65.0-blue)](https://releases.rs/docs/released/1.65.0)
[![CI](https://github.com/mdeloof/statig/actions/workflows/ci.yml/badge.svg)](https://github.com/mdeloof/statig/actions/workflows/ci.yml)


Hierarchical state machines for designing event-driven systems.

**Features**

- Hierachical state machines
- State-local storage
- Compatible with `#![no_std]`, state machines are defined in ROM and no heap memory allocations.
- (Optional) macro's for reducing boilerplate.

---

**Overview**

- [Statig in action](#statig-in-action)
- [Concepts](#concepts)
    - [States](#states)
    - [Superstates](#superstates)
    - [Actions](#actions)
    - [Shared storage](#shared-storage)
    - [State-local storage](#state-local-storage)
    - [Introspection](#introspection)
- [Implementation](#implementation)
- [FAQ](#faq)
- [Credits](#credits)

---

## Statig in action

A simple blinky state machine:

```
┌─────────────────────────┐                   
│         Blinking        │◀─────────┐        
│    ┌───────────────┐    │          │        
│ ┌─▶│     LedOn     │──┐ │  ┌───────────────┐
│ │  └───────────────┘  │ │  │  NotBlinking  │
│ │  ┌───────────────┐  │ │  └───────────────┘
│ └──│     LedOff    │◀─┘ │          ▲        
│    └───────────────┘    │──────────┘        
└─────────────────────────┘                   
```

```rust
#[derive(Default)]
pub struct Blinky;

pub enum Event {
    TimerElapsed,
    ButtonPressed
}

#[state_machine(initial = "State::led_on()")]
impl Blinky {
    #[state(superstate = "blinking")]
    fn led_on(event: &Event) -> Response<State> {
        match event {
            Event::TimerElapsed => Transition(State::led_off()),
            _ => Super
        }
    }

    #[state(superstate = "blinking")]
    fn led_off(event: &Event) -> Response<State> {
        match event {
            Event::TimerElapsed => Transition(State::led_on()),
            _ => Super
        }
    }

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

    #[state]
    fn not_blinking(event: &Event) -> Response<State> {
        match event {
            Event::ButtonPressed => Transition(State::led_on()),
            _ => Super
        }
    }
}

fn main() {
    let mut state_machine = Blinky::default().state_machine().init();

    state_machine.handle(&Event::TimerElapsed);
    state_machine.handle(&Event::ButtonPressed);
}
```

(See the [`macro/blinky`](examples/macro/blinky/src/main.rs) example for the full code with comments. Or see [`no_macro/blinky`](examples/no_macro/blinky/src/main.rs) 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. When an event is submitted to the state machine, the method associated with the current state will be called to process it. By default this event is mapped to the `event` argument of the method.

```rust
#[state]
fn led_on(event: &Event) -> Response<State> {
    Transition(State::led_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 parent superstate.

### Superstates

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

```rust
#[state(superstate = "blinking")]
fn led_on(event: &Event) -> Response<State> {
    match event {
        Event::TimerElapsed => Transition(State::led_off()),
        Event::ButtonPressed => Super
    }
}

#[superstate]
fn blinking(event: &Event) -> Response<State> {
    match event {
        Event::ButtonPressed => Transition(State::not_blinking()),
        _ => Super
    }
}
```

Superstates can themselves also have superstates.

### Actions

Actions run when entering or leaving states during a transition.

```rust
#[state(entry_action = "enter_led_on", exit_action = "exit_led_on")]
fn led_on(event: &Event) -> Response<State> {
    Transition(State::led_off())
}

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

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

### Shared storage

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

```rust
#[state]
fn led_on(&mut self, event: &Event) -> Response<State> {
    match event {
        Event::TimerElapsed => {
            self.led = false;
            Transition(State::led_off())
        }
        _ => Super
    }
}
```

Or alternatively, set `led` inside the entry action.

```rust
#[action]
fn enter_led_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 shared storage and potentially having to unwrap an `Option<T>`, you can add it as an input to your state handler.

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

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

### Introspection

For logging purposes you can define two callbacks that will be called at specific points during state machine execution.

- `on_dispatch` is called before an event is dispatched to a specific state or superstate.
- `on_transition` is called after a transition has occured.

```rust
#[state_machine(
    initial = "State::on()",
    on_dispatch = "Self::on_dispatch",
    on_transition = "Self::on_transition",
    state(derive(Debug)),
    superstate(derive(Debug))
)]
impl Blinky {
    ...
}

impl Blinky {
    fn on_transition(&mut self, source: &State, target: &State) {
        println!("transitioned from `{:?}` to `{:?}`", source, target);
    }

    fn on_dispatch(&mut self, state: StateOrSuperstate<Blinky>, event: &Event) {
        println!("dispatched `{:?}` to `{:?}`", event, state);
    }
}
```

---

## Implementation

A lot of the implemenation details are dealt with by the `#[state_machine]` macro, but it's always valuable to understand what's happening behind the scenes. Furthermore, you'll see that the generated code is actually pretty straight-forward and could easily be written by hand, so if you prefer to avoid using macro's this is totally feasible.

The goal of `statig` is to represent a hierarchical state machine. Conceptually a hierarchical state machine can be tought of as tree.

```
                          ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐             
                                    Top                       
                          └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘             
                                     │                        
                        ┌────────────┴────────────┐           
                        │                         │           
             ┌─────────────────────┐   ╔═════════════════════╗
             │      Blinking       │   ║     NotBlinking     ║
             │─────────────────────│   ╚═════════════════════╝
             │ counter: &'a usize  │                          
             └─────────────────────┘                          
                        │                                     
           ┌────────────┴────────────┐                        
           │                         │                        
╔═════════════════════╗   ╔═════════════════════╗             
║        LedOn        ║   ║        LedOff       ║             
║─────────────────────║   ║─────────────────────║             
║ counter: usize      ║   ║ counter: usize      ║             
╚═════════════════════╝   ╚═════════════════════╝
```

Nodes at the edge of the tree are called leaf-states and are represented by an `enum` in `statig`. If data only exists in a particular state we can give that state ownership of the data. This is referred to as 'state-local storage'. For example `counter` only exists in the `LedOn` and `LedOff` state.

```rust
enum State {
    LedOn { counter: usize },
    LedOff { counter: usize },
    NotBlinking
}
```

States such as `Blinking` are called superstates. They define shared behavior of their child states. Superstates are also represented by an enum, but instead of owning their data, they borrow it from the underlying state.

```rust
enum Superstate<'a> {
    Blinking { counter: &'a usize }
}
```

The association between states and their handlers is then expressed in the `State` and `Superstate` traits with the `call_handler()` method.

```rust
impl statig::State<Blinky> for State {
    fn call_handler(&mut self, blinky: &mut Blinky, event: &Event) -> Response<Self> {
        match self {
            State::LedOn { counter } => blinky.led_on(counter, event),
            State::LedOff { counter } => blinky.led_off(counter, event),
            State::NotBlinking => blinky.not_blinking(event)
        }
    }
}

impl statig::Superstate<Blinky> for Superstate {
    fn call_handler(&mut self, blinky: &mut Blinky, event: &Event) -> Response<Self> {
        match self {
            Superstate::Blinking { counter } => blinky.blinking(counter, event),
        }
    }
}
```

The association between states and their actions is expressed in a similar fashion.

```rust
impl statig::State<Blinky> for State {
    
    ...

    fn call_entry_action(&mut self, blinky: &mut Blinky) {
        match self {
            State::LedOn { counter } => blinky.enter_led_on(counter),
            State::LedOff { counter } => blinky.enter_led_off(counter),
            State::NotBlinking => blinky.enter_not_blinking()
        }
    }

    fn call_exit_action(&mut self, blinky: &mut Blinky) {
        match self {
            State::LedOn { counter } => blinky.exit_led_on(counter),
            State::LedOff { counter } => blinky.exit_led_off(counter),
            State::NotBlinking => blinky.exit_not_blinking()
        }
    }
}

impl statig::Superstate<Blinky> for Superstate {

    ...

    fn call_entry_action(&mut self, blinky: &mut Blinky) {
        match self {
            Superstate::Blinking { counter } => blinky.enter_blinking(counter),
        }
    }

    fn call_exit_action(&mut self, blinky: &mut Blinky) {
        match self {
            Superstate::Blinking { counter } => blinky.exit_blinking(counter),
        }
    }
}
```

The tree structure of states and their superstates is expressed in the `superstate` method of the `State` and `Superstate` trait.

```rust
impl statig::State<Blinky> for State {

    ...

    fn superstate(&mut self) -> Option<Superstate<'_>> {
        match self {
            State::LedOn { counter } => Some(Superstate::Blinking { counter }),
            State::LedOff { counter } => Some(Superstate::Blinking { counter }),
            State::NotBlinking => None
        }
    }
}

impl<'a> statig::Superstate<Blinky> for Superstate<'a> {

    ...

    fn superstate(&mut self) -> Option<Superstate<'_>> {
        match self {
            Superstate::Blinking { .. } => None
        }
    }
}
```

When an event arrives, `statig` will first dispatch it to the current leaf state. If this state returns a `Super` response, it will then be dispatched to that state's superstate, which in turn returns its own response. Every time an event is defered to a superstate, `statig` will traverse upwards in the graph until it reaches the `Top` state. This is an implicit superstate that will consider every event as handled.

In case the returned response is a `Transition`, `statig` will perform a transition sequence by traversing the graph from the current source state to the target state by taking the shortest possible path. When this path is going upwards from the source state, every state that is passed will have its **exit action** executed. And then similarly when going downward, every state that is passed will have its **entry action** executed.

For example when transitioning from the `LedOn` state to the `NotBlinking` state the transition sequence looks like this:

1. Exit the `LedOn` state
2. Exit the `Blinking` state
3. Enter the `NotBlinking` state

For comparison, the transition from the `LedOn` state to the `LedOff` state looks like this:

1. Exit the `LedOn` state
2. Enter the `LedOff` state

We don't execute the exit or entry action of `Blinking` as this superstate is shared between the `LedOn` and `LedOff` state.

Entry and exit actions also have access to state-local storage, but note that exit actions operate on state-local storage of the source state and that entry actions operate on the state-local storage of the target state.

For example chaning the value of `counter` in the exit action of `LedOn` will have no effect on the value of `counter` in the `LedOff` state.

Finally, the `StateMachine` trait is implemented on the type that will be used for the shared storage.

```rust
impl StateMachine for Blinky {
    type State = State;

    type Superstate<'a> = Superstate<'a>;

    type Event<'a> = Event;

    const INITIAL: State = State::off(10);
}
```

---

## 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](./statig/tests/transition_macro.rs) and [without](./statig/tests/transition.rs) macros.

### What advantage does this have over using the typestate pattern?

I would say they serve a different purpose. The [typestate pattern](http://cliffle.com/blog/rust-typestate/) is very useful for designing an API as it is able to enforce the validity of operations at compile time by making each state a unique type. But `statig` is designed to model a dynamic system where events originate externally and the order of operations is determined at run time. More concretely, this means that the state machine is going to sit in a loop where events are read from a queue and submitted to the state machine using the `handle()` method. If we want to do the same with a state machine that uses the typestate pattern we'd have to use an enum to wrap all our different states and match events to operations on these states. This means extra boilerplate code for little advantage as the order of operations is unknown so it can't be checked at compile time. On the other hand `statig` gives you the ability to create a hierarchy of states which I find to be invaluable as state machines grow in complexity.

---

## Credits

The idea for this library came from reading the book [Practical UML Statecharts in C/C++](https://www.state-machine.com/doc/PSiCC2.pdf). I highly recommend it if you want to learn how to use state machines to design complex systems.