statecraft 0.1.0

State machines with compile-time enforced transitions
Documentation
# statecraft

A generic state machine library which aims to enforce transitions between states
at compile time. Inspired by
[Hoverbear](https://hoverbear.org/blog/rust-state-machine-pattern/).


## Overview

Much of this code is intended to
prevent run-time checks of a state machine's state for two primary reasons:
1. Run-time checks can incur a performance penalty
2. Run-time checks are often subject to bugs (though Rust is quite good at
   preventing this with things like [Exhaustive
   Matching](https://rustc-dev-guide.rust-lang.org/pat-exhaustive-checking.html)).

By using this library to implement your state machine, you will have guarantees
from the Rust compiler that your state machine cannot end up in an invalid or
unplanned-for state.

Users of this library will be most interested in the following traits:
- [`State`]: A state that your machine can exist in
- [`Stateful`]: A thing which has some state associated with it. This is likely
  your state machine.
- [`Transitionable`] and [`TryTransitionable`]: Two traits which must be
  implemented to allow your [`Stateful`] to transition between [`State`]s.
- [`AsyncTransitionable`] and [`AsyncTryTransitionable`]: Two `async` variants
  of `Transitionable` and `TryTransitionable` which allow `async` transitions
  between [`State`]s.


## Examples

### Simple Three-State Machine

The following is a three-state machine, which is driven externally and
transitions between each state always succeed. While perhaps unrealistic, it
provides a decent overview to the crate. Transitions go from
`Alpha->Beta->Gamma->Alpha`.

```rust
use statecraft::{State, Stateful, Transitionable};

#[derive(State)]
struct Alpha;

#[derive(State)]
struct Beta;

#[derive(State)]
struct Gamma;

#[derive(Stateful)]
#[state(S)]
struct Machine<S: State> {
    state: S
}

impl Transitionable<Beta> for Machine<Alpha> {
    type NextStateful = Machine<Beta>;

    fn transition(self) -> Self::NextStateful {
        Machine { state: Beta }
    }
}

impl Transitionable<Gamma> for Machine<Beta> {
    type NextStateful = Machine<Gamma>;

    fn transition(self) -> Self::NextStateful {
        Machine { state: Gamma }
    }
}

impl Transitionable<Alpha> for Machine<Gamma> {
    type NextStateful = Machine<Alpha>;

    fn transition(self) -> Self::NextStateful {
        Machine { state: Alpha }
    }
}

let alpha = Machine { state: Alpha };
let beta: Machine<Beta> = alpha.transition();
let gamma: Machine<Gamma> = beta.transition();
let alpha_again: Machine<Alpha> = gamma.transition();
```


### Fallible Transitions

What happens if you have a more complex state machine where the transition
between two states may succeed or **may fail**. That's where the
[`TryTransitionable`] trait comes in. This trait ensures that if there is a
transition from one state to another which can fail, the machine returns back to
a valid state. Imagine our previous example, but the transition from
`Beta->Gamma` can fail. In this case, we want the machine to return to the
`Alpha` state.

```rust
# use statecraft::{Bailable, Recovered, State, Stateful, Transitionable, TryTransitionable};
# 
# #[derive(State, Debug)]
# struct Alpha;
# 
# #[derive(State, Debug)]
# struct Beta;
# 
# #[derive(State, Debug)]
# struct Gamma;
# 
# #[derive(Stateful, Debug)]
# #[state(S)]
# struct Machine<S: State> {
#     state: S
# }
# 
# impl Transitionable<Beta> for Machine<Alpha> {
#     type NextStateful = Machine<Beta>;
# 
#     fn transition(self) -> Self::NextStateful {
#         Machine { state: Beta }
#     }
# }
# 
impl TryTransitionable<Gamma, Alpha> for Machine<Beta> {
    type SuccessStateful = Machine<Gamma>;
    type FailureStateful = Machine<Alpha>;
    type Error = Box<dyn std::error::Error + Send + Sync>;

    fn try_transition(self) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>> {
        // Always fail for test sake
        Err(Recovered::new(Machine { state: Alpha }, "We failed :(".into()))
    }
}

impl Transitionable<Alpha> for Machine<Beta> {
    type NextStateful = Machine<Alpha>;

    fn transition(self) -> Self::NextStateful {
        Machine { state: Alpha }
    }
}
# 
# impl Transitionable<Alpha> for Machine<Gamma> {
#     type NextStateful = Machine<Alpha>;
# 
#     fn transition(self) -> Self::NextStateful {
#         Machine { state: Alpha }
#     }
# }
# 
fn main() {
    let alpha = Machine { state: Alpha };
    let beta: Machine<Beta> = alpha.transition();

    // Try the transition from Beta->Gamma and expect an error
    let alpha_again = beta.try_transition().expect_err("We should be failing!");
}
```


### Async Transitions

Sometimes, the transition between two states may require `async` logic. This is
quite common in state machines which may use `async` I/O. For these cases, we
can use [`AsyncTransitionable`] and [`AsyncTryTransitionable`] respectively.
Consider a slight modification to our original three-state machine.
```rust
use statecraft::*;

#[derive(State, Debug)]
struct Alpha;

#[derive(State, Debug)]
struct Beta;

#[derive(State, Debug)]
struct Gamma;

#[derive(Stateful, SelfTransitionable, Debug)]
#[state(S)]
struct Machine<S: State> {
    state: S
}

impl AsyncTransitionable<Beta> for Machine<Alpha> {
    type NextStateful = Machine<Beta>;

    async fn transition(self) -> Self::NextStateful {
        // We can do some async stuff here
        Machine { state: Beta }
    }
}

impl AsyncTryTransitionable<Gamma, Beta> for Machine<Beta> {
    type SuccessStateful = Machine<Gamma>;
    type FailureStateful = Machine<Beta>;
    type Error = Box<dyn std::error::Error + Send + Sync>;

    async fn try_transition(self) ->  Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>> {
        // We can do some fallible async stuff here
        Ok(Machine { state: Gamma })
    }
}

impl Transitionable<Alpha> for Machine<Gamma> {
    type NextStateful = Machine<Alpha>;

    fn transition(self) -> Self::NextStateful {
        // No async stuff here because we are using Transitionable
        Machine { state: Alpha }
    }
}

#[tokio::main]
async fn main() {
    let alpha = Machine { state: Alpha };
    let beta: Machine<Beta> = transition_async!(alpha, Beta);
    let gamma: Machine<Gamma> = try_transition_async!(beta, Gamma).expect("Failed transition");
    let alpha_again: Machine<Alpha> = transition!(gamma, Alpha);
}
```

Note how we can even mix `async` and non-`async` transitions in the same state
machine.