# 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 `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.