Crates.io | sad_machine |
lib.rs | sad_machine |
version | 1.0.0 |
source | src |
created_at | 2021-09-08 07:09:49.916652 |
updated_at | 2021-09-08 07:09:49.916652 |
description | Sad Machine - a static State Machine macro |
homepage | |
repository | https://github.com/steinuil/sad_machine |
max_upload_size | |
id | 448354 |
size | 51,677 |
sad_machine
provides a macro to declaratively define a state machine
and the transitions between states. It's focused on providing a nice API
for applications that deal with event loops and use a state machine to keep
track of their state.
sad_machine
is a fork of the sm
library which removes the traits and keeps only the macro, and redesigns the
generated code to be more enum-friendly.
sad_machine
exposes only one macro, state_machine!
. A quick example:
use sad_machine::state_machine;
state_machine! {
Lock {
InitialStates { Locked, Unlocked }
TurnKey {
Locked => Unlocked
Unlocked => Locked
}
BreakKeyhole {
Locked, Unlocked => Broken
}
Repair {
Broken => Locked
}
}
}
fn main() {
let mut lock = Lock::locked();
loop {
match lock {
Lock::Locked(m @ LockedState::FromInit) => lock = m.turn_key(),
Lock::Unlocked(m) => lock = m.turn_key(),
Lock::Locked(m) => lock = m.break_keyhole(),
Lock::Broken(_) => break,
}
}
assert_eq!(lock, Lock::Broken(BrokenState::FromBreakKeyhole));
}
In this example, the macro generated:
Lock
containing all states of the enum.Unlocked
state, the enum is called UnlockedState
and contains the two cases FromInit, FromTurnKey
.Lock::locked()
and Lock::unlocked()
,
mirroring the states defined in InitialStates
.Broken
state,
a .repair()
method is generated which mirrors the Repair
event.A few differences from sm
's API:
pub
.The below example explains step-by-step how to create a new state machine using the provided macro, and then how to use the created machine in your code.
First, we import the macro from the crate:
use sad_machine::state_machine;
Next, we initiate the macro declaration:
state_machine! {
Then, provide a name for the machine, and declare a list of allowed initial states:
Lock {
InitialStates { Locked, Unlocked }
Finally, we declare one or more events and the associated transitions:
TurnKey {
Locked => Unlocked
Unlocked => Locked
}
BreakKeyhole {
Locked, Unlocked => Broken
}
}
}
And we're done. We've defined our state machine structure, and the valid transitions, and can now use this state machine in our code.
You can initialise the machine as follows:
let sm = Lock::locked();
We've initialised our machine in the Locked
state. The sm
is as an enum
covering all possible states of the state machine, and each state contains
the name of the event that triggered it. A full pattern match on the state
enum looks like this:
match lock {
Lock::Locked(LockedState::FromInit) => ..,
Lock::Locked(LockedState::FromTurnKey) => ..,
Lock::Locked(LockedState::FromRepair) => ..,
Lock::Unlocked(UnlockedState::FromInit) => ..,
Lock::Unlocked(UnlockedState::FromTurnKey) => ..,
Lock::Broken(BrokenState::FromBreakKeyhole) => ..,
}
To transition this machine to the Unlocked
state, we send the turn_key
method on the LockedState object:
let lock = match lock {
Lock::Locked(locked) => locked.turn_key(),
_ => panic!("wrong state"),
}
The state machine does not consume the previous state when performing
a transition, as opposed to sm
's behavior, so be careful when operating in
a concurrent context.
It also doesn't prevent you from constructing a state that is not one of the initial states, due to Rust's lack of private constructors for enums.
Some of the design choices that sm
makes conflict with my use case.
I was using the library in an event loop where:
Variant
representation, and can only
advance by one step in a single loopsm
seems to have different design goals:
transition
method returns a Machine
type and not an enum, which
forces me to call .as_enum()
on its result every time to store it as
Variant
, but makes it easy to trigger multiple state transitions in
a single piece of codeVariant
enum also include the name of the event that
triggered the state change, which led me to duplicate code in multiple
branches for each state that had multiple entry pointssad_machine
's API focuses on the state enum rather than on concrete states.
The transition methods it generates return the enum, which makes it harder to
trigger multiple transitions in the same piece of code, but on the other hand
it removes the cruft of calling .as_enum()
on the result, and its state enum
does not encode the event name in the name of its cases, but rather carries it
inside itself.
This forks keeps sm
's parser for the DSL to define the state machine and
changes the generated code.
Licensed under either of
This was the license of the original crate and I'd rather not change it.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.