diff --git a/rust-fsm/src/lib.rs b/rust-fsm/src/lib.rs index 2decd67..7755aff 100644 --- a/rust-fsm/src/lib.rs +++ b/rust-fsm/src/lib.rs @@ -1,4 +1,232 @@ -#![doc = include_str!("../../README.md")] +/*! +[![Documentation][docs-badge]][docs-link] +[![Latest Version][crate-badge]][crate-link] + +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 +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. +* 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 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 + +### Default + +- `std` - implement features that require the `std` environment. See below. +- `dsl` - re-export `rust-fsm-dsl` from `rust-fsm`. Recommended to leave this on + for the best development experience. + +### Non-default + +- `diagram` - generate Mermaid state diagrams in the doc strings. See below. + +## 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 +`rust-fsm = { version = "0.7", 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`). + +The DSL implementation re-export is gated by the feature named `dsl` which is +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). + +### Using the DSL for defining state machines + +The DSL is parsed by the `state_machine` macro. Here is a little example. + +```rust +use rust_fsm::*; + +state_machine! { + #[derive(Debug)] + #[repr(C)] + /// A Circuit Breaker state machine. + circuit_breaker(Closed) + + Closed(Unsuccessful) => Open [SetupTimer], + Open(TimerTriggered) => HalfOpen, + HalfOpen => { + Successful => Closed, + Unsuccessful => Open [SetupTimer] + } +} +``` + +This code sample: + +* Defines a state machine called `circuit_breaker`; +* 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`. + +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(); +// Consume the `Successful` input. No state transition is performed. +let _ = machine.consume(&circuit_breaker::Input::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(); +// Check the output +if let Some(circuit_breaker::Output::SetupTimer) = output { + // Set up the timer... +} +// Check the state +if let circuit_breaker::State::Open = machine.state() { + // 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 + output alphabet respectively. +* Type alias `circuit_breaker::StateMachine` that expands to +`StateMachine`. + +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. + +#### Visibility + +You can specify visibility like this: + +```rust +use rust_fsm::*; + +state_machine! { + pub CircuitBreaker(Closed) + + Closed(Unsuccessful) => Open [SetupTimer], + Open(TimerTriggered) => HalfOpen, + HalfOpen => { + Successful => Closed, + Unsuccessful => Open [SetupTimer], + } +} +``` + +The default visibility is private. + +#### Custom alphabet types + +You can supply your own types to use as input, output or state. All of them are +optional: you can use only one of them or all of them at once if you want to. +The current limitation is that you have to supply a fully qualified type path. + +```rust,ignore +use rust_fsm::*; + +pub enum Input { + Successful, + Unsuccessful, + TimerTriggered, +} + +pub enum State { + Closed, + HalfOpen, + Open, +} + +pub enum Output { + SetupTimer, +} + +state_machine! { + #[state_machine(input(crate::Input), state(crate::State), output(crate::Output))] + circuit_breaker(Closed) + + Closed(Unsuccessful) => Open [SetupTimer], + Open(TimerTriggered) => HalfOpen, + HalfOpen => { + Successful => Closed, + Unsuccessful => Open [SetupTimer] + } +} +``` + +#### Diagrams + +`state_machine` macro can document your state machines with diagrams. This is +controlled by the `diagram` feature, which is non-default. The diagrams are +generated in the [Mermaid][mermaid] format. This feature includes the Mermaid +script into the documentation page. + +To see this in action, download the repository and run: + +```bash +cargo doc -p doc-example --open +``` + +![image](doc-diagram-example.png) + +### 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. + +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]. + +[repo]: https://github.com/eugene-babichenko/rust-fsm +[docs-badge]: https://docs.rs/rust-fsm/badge.svg +[docs-link]: https://docs.rs/rust-fsm +[crate-badge]: https://img.shields.io/crates/v/rust-fsm.svg +[crate-link]: https://crates.io/crates/rust-fsm +[mermaid]: https://mermaid.js.org/ +*/ + #![cfg_attr(not(feature = "std"), no_std)] use core::fmt;