Finite state machines in rust; bendns fork to add types.
fix docs
| -rw-r--r-- | README.md | 100 | ||||
| -rw-r--r-- | rust-fsm-dsl/src/lib.rs | 2 | ||||
| -rw-r--r-- | rust-fsm/src/lib.rs | 67 | ||||
| -rw-r--r-- | rust-fsm/tests/circuit_breaker_dsl.rs | 2 | ||||
| -rw-r--r-- | rust-fsm/tests/circuit_breaker_dsl_custom_types.rs | 6 | ||||
| -rw-r--r-- | rust-fsm/tests/simple.rs | 2 |
6 files changed, 70 insertions, 109 deletions
@@ -1,36 +1,21 @@ -# A framework and a DSL for building finite state machines in Rust - [![Documentation][docs-badge]][docs-link] [![Latest Version][crate-badge]][crate-link] -[](https://www.buymeacoffee.com/ybabichenko) - The `rust-fsm` crate provides a simple and universal framework for building state machines in Rust with minimum effort. -The essential part of this crate is the -[`StateMachineImpl`](trait.StateMachineImpl.html) trait. This trait allows a +The essential part of this crate is the [`StateMachine`] trait. This trait allows a developer to provide a strict state machine definition, e.g. specify its: -- An input alphabet - a set of entities that the state machine takes as inputs - and performs state transitions based on them. +- An input alphabet - a set of entities that the state machine takes as + inputs and performs state transitions based on them. - Possible states - a set of states this machine could be in. -- An output alphabet - a set of entities that the state machine may output as - results of its work. -- A transition function - a function that changes the state of the state machine - based on its current state and the provided input. +- An output alphabet - a set of entities that the state machine may output + as results of its work. +- A transition function - a function that changes the state of the state + machine based on its current state and the provided input. - An output function - a function that outputs something from the output alphabet based on the current state and the provided inputs. -- The initial state of the machine. - -Note that on the implementation level such abstraction allows build any type of -state machines: - -- A classical state machine by providing only an input alphabet, a set of states - and a transition function. -- A Mealy machine by providing all entities listed above. -- A Moore machine by providing an output function that do not depend on the - provided inputs. ## Feature flags @@ -46,8 +31,8 @@ state machines: ## Usage in `no_std` environments -This library has the feature named `std` which is enabled by default. You may -want to import this library as +This library has the feature named `std` which is enabled by default. You +may want to import this library as `rust-fsm = { version = "0.8", default-features = false, features = ["dsl"] }` to use it in a `no_std` environment. This only affects error types (the `Error` trait is only available in `std`). @@ -58,9 +43,9 @@ also enabled by default. ## Use Initially this library was designed to build an easy to use DSL for defining -state machines on top of it. Using the DSL will require to connect an additional -crate `rust-fsm-dsl` (this is due to limitation of the procedural macros -system). +state machines on top of it. Using the DSL will require to connect an +additional crate `rust-fsm-dsl` (this is due to limitation of the procedural +macros system). ### Using the DSL for defining state machines @@ -73,7 +58,7 @@ state_machine! { #[derive(Debug)] #[repr(C)] /// A Circuit Breaker state machine. - circuit_breaker(Closed) + CircuitBreaker => Result => Action Closed => Unsuccessful => Open [SetupTimer], Open => TimerTriggered => HalfOpen, @@ -86,46 +71,40 @@ state_machine! { This code sample: -- Defines a state machine called `circuit_breaker`; +- Defines a state machine called `CircuitBreaker`; - Derives the `Debug` trait for it. All attributes you use here (like - `#[repr(C)]`) will be applied to all types generated by this macro. If you - want to apply attributes or a docstring to the `mod` generated by this macro, - just put it before the macro invocation. -- Sets the initial state of this state machine to `Closed`; -- Defines state transitions. For example: on receiving the `Successful` input - when in the `HalfOpen` state, the machine must move to the `Closed` state; -- Defines outputs. For example: on receiving `Unsuccessful` in the `Closed` - state, the machine must output `SetupTimer`. + `#[repr(C)]`) will be applied to all types generated by this macro. +- Defines state transitions. For example: on receiving the `Successful` + input when in the `HalfOpen` state, the machine must move to the `Closed` + state; +- Defines outputs. For example: on receiving `Unsuccessful` in the + `Closed` state, the machine must output `SetupTimer`. This state machine can be used as follows: ```rust,ignore // Initialize the state machine. The state is `Closed` now. -let mut machine = circuit_breaker::StateMachine::new(); +let mut machine = CircuitBreaker::Closed; // Consume the `Successful` input. No state transition is performed. -let _ = machine.consume(circuit_breaker::Input::Successful); +let _ = machine.consume(Result::Successful); // Consume the `Unsuccesful` input. The machine is moved to the `Open` // state. The output is `SetupTimer`. -let output = machine.consume(circuit_breaker::Input::Unsuccessful).unwrap(); +let output = machine.consume(Result::Unsuccessful).unwrap(); // Check the output -if let Some(circuit_breaker::Output::SetupTimer) = output { +if let Some(Action::SetupTimer) = output { // Set up the timer... } // Check the state -if let circuit_breaker::State::Open = machine.state() { +if let CircuitBreaker::Open = machine { // Do something... } ``` The following entities are generated: -- An empty structure `circuit_breaker::Impl` that implements the - `StateMachineImpl` trait. -- Enums `circuit_breaker::State`, `circuit_breaker::Input` and - `circuit_breaker::Output` that represent the state, the input alphabet and the +- Enums `CircuitBreaker`, `Result` and + `Action` that represent the state, the input alphabet and the output alphabet respectively. -- Type alias `circuit_breaker::StateMachine` that expands to - `StateMachine<circuit_breaker::Impl>`. Note that if there is no outputs in the specification, the output alphabet is an empty enum and due to technical limitations of many Rust attributes, no @@ -141,13 +120,13 @@ You can specify visibility like this: use rust_fsm::*; state_machine! { - pub CircuitBreaker(Closed) + pub CircuitBreaker => Result => Action - Closed(Unsuccessful) => Open [SetupTimer], - Open(TimerTriggered) => HalfOpen, + Closed => Unsuccessful => Open [SetupTimer], + Open => TimerTriggered => HalfOpen, HalfOpen => { Successful => Closed, - Unsuccessful => Open [SetupTimer], + Unsuccessful => Open [SetupTimer] } } ``` @@ -180,11 +159,10 @@ pub enum Output { } state_machine! { - #[state_machine(input(crate::Input), state(crate::State), output(crate::Output))] - circuit_breaker(Closed) + crate::State => crate::Input => crate::Output - Closed(Unsuccessful) => Open [SetupTimer], - Open(TimerTriggered) => HalfOpen, + Closed => Unsuccessful => Open [SetupTimer], + Open => TimerTriggered => HalfOpen, HalfOpen => { Successful => Closed, Unsuccessful => Open [SetupTimer] @@ -209,16 +187,16 @@ cargo doc -p doc-example --open ### Without DSL -The `state_machine` macro has limited capabilities (for example, a state cannot -carry any additional data), so in certain complex cases a user might want to -write a more complex state machine by hand. +The `state_machine` macro has limited capabilities (for example, a state +cannot carry any additional data), so in certain complex cases a user might +want to write a more complex state machine by hand. All you need to do to build a state machine is to implement the `StateMachineImpl` trait and use it in conjuctions with some of the provided wrappers (for now there is only `StateMachine`). -You can see an example of the Circuit Breaker state machine in the [project -repository][repo]. +You can see an example of the Circuit Breaker state machine in the +[project repository][repo]. [repo]: https://github.com/eugene-babichenko/rust-fsm [docs-badge]: https://docs.rs/rust-fsm/badge.svg diff --git a/rust-fsm-dsl/src/lib.rs b/rust-fsm-dsl/src/lib.rs index ed1d729..40a487d 100644 --- a/rust-fsm-dsl/src/lib.rs +++ b/rust-fsm-dsl/src/lib.rs @@ -225,7 +225,7 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { #state_impl #output_impl - impl ::rust_fsm::StateMachineImpl for #state_name { + impl ::rust_fsm::StateMachine for #state_name { type Input = #input_name; type Output = #output_name; diff --git a/rust-fsm/src/lib.rs b/rust-fsm/src/lib.rs index ed3d338..1ab4c74 100644 --- a/rust-fsm/src/lib.rs +++ b/rust-fsm/src/lib.rs @@ -5,8 +5,7 @@ The `rust-fsm` crate provides a simple and universal framework for building state machines in Rust with minimum effort. -The essential part of this crate is the -[`StateMachineImpl`](trait.StateMachineImpl.html) trait. This trait allows a +The essential part of this crate is the [`StateMachine`] trait. This trait allows a developer to provide a strict state machine definition, e.g. specify its: * An input alphabet - a set of entities that the state machine takes as @@ -18,16 +17,6 @@ developer to provide a strict state machine definition, e.g. specify its: machine based on its current state and the provided input. * An output function - a function that outputs something from the output alphabet based on the current state and the provided inputs. -* The initial state of the machine. - -Note that on the implementation level such abstraction allows build any type -of state machines: - -* A classical state machine by providing only an input alphabet, a set of - states and a transition function. -* A Mealy machine by providing all entities listed above. -* A Moore machine by providing an output function that do not depend on the - provided inputs. ## Feature flags @@ -70,10 +59,10 @@ state_machine! { #[derive(Debug)] #[repr(C)] /// A Circuit Breaker state machine. - circuit_breaker(Closed) + CircuitBreaker => Result => Action - Closed(Unsuccessful) => Open [SetupTimer], - Open(TimerTriggered) => HalfOpen, + Closed => Unsuccessful => Open [SetupTimer], + Open => TimerTriggered => HalfOpen, HalfOpen => { Successful => Closed, Unsuccessful => Open [SetupTimer] @@ -83,12 +72,9 @@ state_machine! { This code sample: -* Defines a state machine called `circuit_breaker`; +* Defines a state machine called `CircuitBreaker`; * Derives the `Debug` trait for it. All attributes you use here (like - `#[repr(C)]`) will be applied to all types generated by this macro. If you - want to apply attributes or a docstring to the `mod` generated by this macro, - just put it before the macro invocation. -* Sets the initial state of this state machine to `Closed`; + `#[repr(C)]`) will be applied to all types generated by this macro. * Defines state transitions. For example: on receiving the `Successful` input when in the `HalfOpen` state, the machine must move to the `Closed` state; @@ -99,38 +85,33 @@ This state machine can be used as follows: ```rust,ignore // Initialize the state machine. The state is `Closed` now. -let mut machine = circuit_breaker::StateMachine::new(); +let mut machine = CircuitBreaker::Closed; // Consume the `Successful` input. No state transition is performed. -let _ = machine.consume(circuit_breaker::Input::Successful); +let _ = machine.consume(Result::Successful); // Consume the `Unsuccesful` input. The machine is moved to the `Open` // state. The output is `SetupTimer`. -let output = machine.consume(circuit_breaker::Input::Unsuccessful).unwrap(); +let output = machine.consume(Result::Unsuccessful).unwrap(); // Check the output -if let Some(circuit_breaker::Output::SetupTimer) = output { +if let Some(Action::SetupTimer) = output { // Set up the timer... } // Check the state -if let circuit_breaker::State::Open = machine.state() { +if let CircuitBreaker::Open = machine { // Do something... } ``` The following entities are generated: -* An empty structure `circuit_breaker::Impl` that implements the - `StateMachineImpl` trait. -* Enums `circuit_breaker::State`, `circuit_breaker::Input` and - `circuit_breaker::Output` that represent the state, the input alphabet and the +* Enums `CircuitBreaker`, `Result` and + `Action` that represent the state, the input alphabet and the output alphabet respectively. -* Type alias `circuit_breaker::StateMachine` that expands to - `StateMachine<circuit_breaker::Impl>`. Note that if there is no outputs in the specification, the output alphabet is an empty enum and due to technical limitations of many Rust attributes, no attributes (e.g. `derive`, `repr`) are applied to it. -Within the `state_machine` macro you must define at least one state -transition. +Within the `state_machine` macro you must define at least one state transition. #### Visibility @@ -140,7 +121,7 @@ You can specify visibility like this: use rust_fsm::*; state_machine! { - pub CircuitBreaker: Closed => Result => Action + pub CircuitBreaker => Result => Action Closed => Unsuccessful => Open [SetupTimer], Open => TimerTriggered => HalfOpen, @@ -179,11 +160,10 @@ pub enum Output { } state_machine! { - #[state_machine(input(crate::Input), state(crate::State), output(crate::Output))] - circuit_breaker(Closed) + crate::State => crate::Input => crate::Output - Closed(Unsuccessful) => Open [SetupTimer], - Open(TimerTriggered) => HalfOpen, + Closed => Unsuccessful => Open [SetupTimer], + Open => TimerTriggered => HalfOpen, HalfOpen => { Successful => Closed, Unsuccessful => Open [SetupTimer] @@ -247,21 +227,24 @@ pub use aquamarine::aquamarine; /// machine/transducer. This is just a formal definition that may be /// inconvenient to be used in practical programming, but it is used throughout /// this library for more practical things. -pub trait StateMachineImpl: Sized { +pub trait StateMachine: Sized { /// The input alphabet. type Input; /// The output alphabet. type Output; /// The transition fuction that outputs a new state based on the current - /// state and the provided input. Outputs `None` when there is no transition - /// for a given combination of the input and the state. Also gives you the output, if any. + /// state and the provided input. Outputs [`Err`] (allowing recovery of the state and input) + /// when there is no transition for a given combination of the input and the state. + /// Also gives you the output, if any. + /// + /// This function is discouraged from panicking. fn transition( self, input: Self::Input, ) -> Result<(Self, Option<Self::Output>), TransitionImpossibleError<Self, Self::Input>>; /// Consumes the provided input, gives an output and performs a state /// transition. If a state transition with the current state and the - /// provided input is not allowed, returns an error. + /// provided input is not allowed, returns an error containing the input. /// /// Aborts if `transition` panics. fn consume( diff --git a/rust-fsm/tests/circuit_breaker_dsl.rs b/rust-fsm/tests/circuit_breaker_dsl.rs index 0e58486..8d5e0f3 100644 --- a/rust-fsm/tests/circuit_breaker_dsl.rs +++ b/rust-fsm/tests/circuit_breaker_dsl.rs @@ -9,7 +9,7 @@ state_machine! { /// A dummy implementation of the Circuit Breaker pattern to demonstrate /// capabilities of its library DSL for defining finite state machines. /// https://martinfowler.com/bliki/CircuitBreaker.html - #[derive(Clone, Copy, Debug)] + #[derive(Debug)] pub CircuitBreaker => Result => Action Closed => Unsuccessful => Open [SetupTimer], diff --git a/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs b/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs index bdf6829..389f913 100644 --- a/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs +++ b/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs @@ -5,21 +5,21 @@ use rust_fsm::*; use std::sync::{Arc, Mutex}; use std::time::Duration; -#[derive(Clone, Copy, Debug)] +#[derive(Debug)] pub enum Input { Successful, Unsuccessful, TimerTriggered, } -#[derive(Clone, Copy, Debug)] +#[derive(Debug)] pub enum State { Closed, HalfOpen, Open, } -#[derive(Clone, Copy, Debug)] +#[derive(Debug)] pub enum Output { SetupTimer, } diff --git a/rust-fsm/tests/simple.rs b/rust-fsm/tests/simple.rs index 6f4a7b5..3810dcf 100644 --- a/rust-fsm/tests/simple.rs +++ b/rust-fsm/tests/simple.rs @@ -1,7 +1,7 @@ use rust_fsm::*; state_machine! { - #[derive(Debug, Clone, Copy)] + #[derive(Debug)] #[repr(C)] Door => Action => __ |