# Finite state machine
Create finite state machines in rust with macros. The macro generates a struct, event enums and traits and also a decider trait. The decider trait is used to decide which state to transition to. Each state has a function which is called automatically when the state is entered. An invalid state and an end state are automatically added. You also need to implement a function for each transition. The macro automatically generates traits for everything you need to implement so the validity is checked on compile time.
## Usage
```rust
use finite_state_machine::state_machine;
// Debug is only needed if the verbose feature is enabled
#[derive(Debug, Default)]
struct Data {
...
}
state_machine!(
// The name of the state machine and the type of the data, you can also use livetimes here
CircuitBreaker(Data);
// the first state will automatically become the start state, no matter the name
Closed {
Ok => Closed, // on Ok event go to Closed state
AmperageTooHigh => Open // on AmperageTooHigh event go to open state
},
Open {
AttemptReset => HalfOpen,
Wait => Open
},
HalfOpen {
Success => Closed,
AmperageTooHigh => Open,
MaxAttemps => End
}
);
use circuit_breaker::*;
// now you need to implement the decider trait which emits events which decide which state to transition to
impl Deciders for CircuitBreaker {
fn closed(&self) -> circuit_breaker::ClosedEvents {
if self.data.current_amperage > self.data.max_amperage {
circuit_breaker::ClosedEvents::AmperageTooHigh
} else {
circuit_breaker::ClosedEvents::Ok
}
}
...
}
// now we need to implement the transition trait for each state
impl ClosedTransitions for CircuitBreaker {
fn amperage_too_high(&mut self) -> Result<(), &'static str> {
self.data.tripped_at = Some(SystemTime::now());
Ok(())
}
fn ok(&mut self) -> Result<(), &'static str> {
self.data.current_amperage += 1;
std::thread::sleep(Duration::from_millis(500));
Ok(())
}
fn illegal(&mut self) {}
}
```
For more details, check the examples folder.
## How it works
- The macro will create a module with the name of the state machine which is transformed to snake case. In the examples case: `CircuitBreaker` -> `circuit_breaker`.
- The macro will generate a deciders trait. This trait has a function for each state. You need to implement this trait for your struct. In our case `impl Deciders for CircuitBreaker`. The decider functions only get a non mutable reference `&self` and must return an enum with a variant for each transition. This enum is generated by the macro. In our case `circuit_breaker::ClosedEvents`, etc.
There is also an `Illegal` variant which is used when you encounter a state of the struct which is illegal/impossible. This will transition to the `Invalid` state. In an isolated state machine this should never happen. But if you use the state machine in a larger system, this can happen.
- For each state you create, a `<StateName>Transitions` trait will be generated which you need to implement for your struct. In our case `impl ClosedTransitions for CircuitBreaker`. These functions get a mutable reference to self `&mut self` and must return a `Result<(), &'static str>`. The `&'static str` is used to return an error message. If you return an `Err` the state machine will transition to the invalid state which is automatically added. When entering this state the machine stops and returns to you the error message.
## Debugging
You can enable the feature `verbose` to get more information about the state machine. This will print the current state, the transition and the current data.