//! # What & why? //! //! Most processes that occur in software applications can be modeled //! as finite-state machines (FSMs), however the actual states, the //! transitions between them and the model's interaction with the //! external world is often implicit. //! //! Making the states of a process explicit using a simple language //! that works for both software developers and other people who may //! have opinions on processes makes it easier to synchronise thoughts, //! extend software and keep a good level of control over what is going //! on. //! //! This library aims to provide functionality for implementing //! finite-state machines in a way that balances expressivity and //! safety. //! //! Finito does not aim to prevent every possible incorrect //! transition, but aims for somewhere "safe-enough" (please don't //! lynch me) that is still easily understood. //! //! # Conceptual overview //! //! The core idea behind Finito can be expressed in a single line and //! will potentially look familiar if you have used Erlang in a //! previous life. The syntax used here is the type-signature notation //! of Haskell. //! //! ```text //! advance :: state -> event -> (state, [action]) //! ``` //! //! In short, every FSM is made up of three distinct types: //! //! * a state type representing all possible states of the machine //! //! * an event type representing all possible events in the machine //! //! * an action type representing a description of all possible //! side-effects of the machine //! //! Using the definition above we can now say that a transition in a //! state-machine, involving these three types, takes an initial state //! and an event to apply it to and returns a new state and a list of //! actions to execute. //! //! With this definition most processes can already be modeled quite //! well. Two additional functions are required to make it all work: //! //! ```text //! -- | The ability to cause additional side-effects after entering //! -- a new state. //! > enter :: state -> [action] //! ``` //! //! as well as //! //! ```text //! -- | An interpreter for side-effects //! act :: action -> m [event] //! ``` //! //! **Note**: This library is based on an original Haskell library. In //! Haskell, side-effects can be controlled via the type system which //! is impossible in Rust. //! //! Some parts of Finito make assumptions about the programmer not //! making certain kinds of mistakes, which are pointed out in the //! documentation. Unfortunately those assumptions are not //! automatically verifiable in Rust. //! //! == Example //! //! Please consult `finito-door` for an example representing a simple, //! lockable door as a finite-state machine. This gives an overview //! over Finito's primary features. //! //! If you happen to be the kind of person who likes to learn about //! libraries by reading code, you should familiarise yourself with the //! door as it shows up as the example in other finito-related //! libraries, too. //! //! # Persistence, side-effects and mud //! //! These three things are inescapable in the fateful realm of //! computers, but Finito separates them out into separate libraries //! that you can drag in as you need them. //! //! Currently, those libraries include: //! //! * @finito@: Core components and classes of Finito //! //! * @finito-in-mem@: In-memory implementation of state machines //! that do not need to live longer than an application using //! standard library concurrency primitives. //! //! * @finito-postgres@: Postgres-backed, persistent implementation //! of state machines that, well, do need to live longer. Uses //! Postgres for concurrency synchronisation, so keep that in mind. //! //! Which should cover most use-cases. Okay, enough prose, lets dive //! in. //! //! # Does Finito make you want to scream? //! //! Please reach out! I want to know why! use std::mem; /// Primary trait that needs to be implemented for every state type /// representing the states of an FSM. /// /// This trait is used to implement transition logic and to "tie the /// room together", with the room being our triplet of types. pub trait FSM where Self: Sized { /// A human-readable string uniquely describing what this FSM /// models. This is used in log messages, database tables and /// various other things throughout Finito. const FSM_NAME: &'static str; /// The associated event type of an FSM represents all possible /// events that can occur in the state-machine. type Event; /// The associated action type of an FSM represents all possible /// actions that can occur in the state-machine. type Action; /// `handle` deals with any incoming events to cause state /// transitions and emit actions. This function is the core logic /// of any state machine. /// /// Implementations of this function **must not** cause any /// side-effects to avoid breaking the guarantees of Finitos /// conceptual model. fn handle(self, event: Self::Event) -> (Self, Vec); /// `enter` is called when a new state is entered, allowing a /// state to produce additional side-effects. /// /// This is useful for side-effects that event handlers do not /// need to know about and for resting assured that a certain /// action has been caused when a state is entered. /// /// FSM state types are expected to be enum (i.e. sum) types. A /// state is considered "new" and enter calls are run if is of a /// different enum variant. fn enter(&self) -> Vec; /// `act` interprets and executes FSM actions. This is the only /// part of an FSM in which side-effects are allowed. fn act(Self::Action) -> Vec; } /// This function is the primary function used to advance a state /// machine. It takes care of both running the event handler as well /// as possible state-enter calls and returning the result. /// /// Users of Finito should basically always use this function when /// advancing state-machines manually, and never call FSM-trait /// methods directly. pub fn advance(state: S, event: S::Event) -> (S, Vec) { // Determine the enum variant of the initial state (used to // trigger enter calls). let old_discriminant = mem::discriminant(&state); let (new_state, mut actions) = state.handle(event); // Compare the enum variant of the resulting state to the old one // and run `enter` if they differ. let new_discriminant = mem::discriminant(&new_state); let mut enter_actions = if old_discriminant != new_discriminant { new_state.enter() } else { vec![] }; actions.append(&mut enter_actions); (new_state, actions) }