Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
S

state_machine_future

> 编程语言
Open source

Easily create type-safe `Future`s from state machines — without the boilerplate.

331 stars0 likes0 views
WebsiteGitHub

About

Easily create type-safe `Future`s from state machines — without the boilerplate.

state_machine_future

Easily create type-safe Futures from state machines — without the boilerplate.

state_machine_future type checks state machines and their state transitions, and then generates Future implementations and typestate[0][] boilerplate for you.

  • Introduction
  • Guide
  • Example
  • Attributes
  • Macro
  • Features
  • License
  • Contribution

Introduction

Most of the time, using Future combinators like map and then are a great way to describe an asynchronous computation. Other times, the most natural way to describe the process at hand is a state machine.

When writing state machines in Rust, we want to leverage the type system to enforce that only valid state transitions may occur. To do that, we want typestates[0][]: types that represents each state in the state machine, and methods whose signatures only permit valid state transitions. But we also need an enum of every possible state, so we can treat the whole state machine as a single entity, and implement Future for it. But this is getting to be a lot of boilerplate...

Enter #[derive(StateMachineFuture)].

With #[derive(StateMachineFuture)], we describe the states and the possible transitions between them, and then the custom derive generates:

  • A typestate for each state in the state machine.

  • A type for the whole state machine that implements Future.

  • A concrete start method that constructs the state machine Future for you, initialized to its start state.

  • A state transition polling trait, with a poll_zee_choo method for each non-final state ZeeChoo. This trait describes the state machine's valid transitions, and its methods are called by Future::poll.

Then, all we need to do is implement the generated state transition polling trait.

Additionally, #[derive(StateMachineFuture)] will statically prevent against some footguns that can arise when writing state machines:

  • Every state is reachable from the start state: there are no useless states.

  • There are no states which cannot reach a final state. These states would otherwise lead to infinite loops.

  • All state transitions are valid. Attempting to make an invalid state transition fails to type check, thanks to the generated typestates.

Guide

Describe the state machine's states with an enum and add #[derive(StateMachineFuture)] to it:

#[derive(StateMachineFuture)]
enum MyStateMachine {
    // ...
}

There must be one start state, which is the initial state upon construction; one ready state, which corresponds to Future::Item; and one error state, which corresponds to Future::Error.

#[derive(StateMachineFuture)]
enum MyStateMachine {
    #[state_machine_future(start)]
    Start,

    // ...

    #[state_machine_future(ready)]
    Ready(MyItem),

    #[state_machine_future(error)]
    Error(MyError),
}

Any other variants of the enum are intermediate states.

We define which state-to-state transitions are valid with #[state_machine_future(transitions(...))]. This attribute annotates a state variant, and lists which other states can be transitioned to immediately after this state.

A final state (either ready or error) must be reachable from every intermediate state and the start state. Final states are not allowed to have transitions.

#[derive(StateMachineFuture)]
enum MyStateMachine {
    #[state_machine_future(start, transitions(Intermediate))]
    Start,

    #[state_machine_future(transitions(Start, Ready))]
    Intermediate { x: usize, y: usize },

    #[state_machine_future(ready)]
    Ready(MyItem),

    #[state_machine_future(error)]
    Error(MyError),
}

From this state machine description, the custom derive generates boilerplate for us.

For each state, the custom derive creates:

  • A typestate for the state. The type's name matches the variant name, for example the Intermediate state variant's typestate is also named Intermediate. The kind of struct type generated matches the variant kind: a unit-style variant results in a unit struct, a tuple-style variant results in a tuple struct, and a struct-style variant results in a normal struct with fields.
State enum Variant Generated Typestate
enum StateMachine { MyState, ... } struct MyState;
enum StateMachine { MyState(bool, usize), ... } struct MyState(bool, usize);
enum StateMachine { MyState { x: usize }, ... } struct MyState { x: usize };
  • An enum for the possible states that can come after this state. This enum is named AfterX where X is the state's name. There is also a From implementation for each Y state that can be transitioned to after X. For example, the Intermediate state would get:
enum AfterIntermediate {
    Start(Start),
    Ready(Ready),
}

impl From for AfterIntermediate {
    // ...
}

impl From for AfterIntermediate {
    // ...
}

Next, for the state machine as a whole, the custom derive generates:

  • A state machine Future type, which is essentially an enum of all the different typestates. This type is named BlahFuture where Blah is the name of the state machine description enum. In this example, where the state machine description is named MyStateMachine, the generated state machine future type would be named MyStateMachineFuture.

  • A polling trait, PollBordle where Bordle is this state machine description's name. For each non-final state TootWasabi, this trait has a method, poll_toot_wasabi, which is like Future::poll but specialized to the current state. Each method takes conditional ownership of its state (via [RentToOwn][rent_to_own]) and returns a futures::Poll where Error is the state machine's error type. This signature does not allow invalid state transitions, which makes attempting an illegal state transition fail to type check. Here is the MyStateMachine's polling trait, for example:

trait PollMyStateMachine {
    fn poll_start(
        start: &'a mut RentToOwn,
    ) -> Poll;

    fn poll_intermediate(
        intermediate: &'a mut RentToOwn,
    ) -> Poll;
}
  • An implementation of Future for that type. This implementation dispatches to the appropriate polling trait method depending on what state the future is in:

    • If the Future is in the Start state, then it uses ::poll_start.

    • If it is in the Intermediate state, then it uses ::poll_intermediate.

    • Etc...

  • A concrete start method for the description type (so MyStateMachine::start in this example) which constructs a new state machine Future type in its start state for you. This method has a parameter for each field in the start state variant.

Start enum Variant Generated start Method
MyStart, fn start() -> MyStateMachineFuture { ... }
MyStart(bool, usize), fn start(arg0: bool, arg1: usize) -> MyStateMachineFuture { ... }
MyStart { x: char, y: bool }, fn start(x: char, y: bool) -> MyStateMachineFuture { ... }

Given all those generated types and traits, all we have to do is impl PollBlah for Blah for our state machine Blah.

impl PollMyStateMachine for MyStateMachine {
    fn poll_start(
        start: &'a mut RentToOwn
    ) -> Poll {
        // Call `try_ready!(start.inner.poll())` with any inner futures here.
        //
        // If we're ready to transition states, then we should return
        // `Ok(Async::Ready(AfterStart))`. If we are not ready to transition
        // states, return `Ok(Async::NotReady)`. If we encounter an error,
        // return `Err(...)`.
    }

    fn poll_intermediate(
        intermediate: &'a mut RentToOwn
    ) -> Poll {
        // Same deal as above...
    }
}

Context

The state machine also allows to pass in a context that is available in every poll_* method without having to explicitly include it in every one.

The context can be specified through the context argument of the state_machine_future attribute. This will add parameters to the start method as well as to each poll_* method of the trait.

…

Same as for the state argument, the context can be taken through the RentToOwn type! However, be aware that once you take the context, the state machine will always return Async::NotReady without invoking the poll_ methods anymore. The one exception to this is when the state machine is in a ready or error state, where it will resolve normally when polled if the context has been taken.

That's it!

Example

Here is an example of a simple turn-based game played by two players over HTTP.

#[macro_use]
extern crate state_machine_future;

#[macro_use]
extern crate futures;

use futures::{Async, Future, Poll};
use state_machine_future::RentToOwn;

/// The result of a game.
pub struct GameResult {
    winner: Player,
    loser: Player,
}

/// Some kind of simple turn based game.
///
/// ```text
///              Invite
///                |
///                |
///                | accept invitation
///                |
///                |
///                V
///           WaitingForTurn --------+
///                |   ^             |
///                |   |             | receive turn
///                |   |             |
///                |   +-------------+
/// game concludes |
///                |
///                |
///                |
///                V
///            Finished
/// 

…


### Attributes

This is a list of all of the attributes used by `state_machine_future`:

* `#[derive(StateMachineFuture)]`: Placed on an `enum` that describes a state
machine.

* `#[state_machine_future(derive(Clone, Debug, ...))]`: Placed on the `enum`
that describes the state machine. This attribute describes which
`#[derive(...)]`s to place on the generated `Future` type.

* `#[state_machine_future(start)]`: Used on a variant of the state machine
description `enum`. There must be exactly one variant with this attribute. This
describes the initial starting state. The generated `start` method has a
parameter for each field in this variant.

* `#[state_machine_future(ready)]`: Used on a variant of the state machine
description `enum`. There must be exactly one variant with this attribute. It
must be a tuple-style variant with one field, for example `Ready(MyItemType)`.
The generated `Future` implementation uses the field's type as `Future::Item`.

* `#[state_machine_future(error)]`: Used on a variant of the state machine
description `enum`. There must be exactly one variant with this attribute. It
must be a tuple-style variant with one field, for example `Error(MyError)`.  The
generated `Future` implementation uses the field's type as `Future::Error`.

* `#[state_machine_future(transitions(OtherState, AnotherState, ...))]`: Used on
a variant of the state machine description `enum`. Describes the states that
this one can transition to.

### Macro

An auxiliary macro is provided that helps reducing boilerplate code for state
transitions. So, the following code:

```Ok(Ready(NextState(1).into()))```

Can be reduced to:

```transition!(NextState(1))```

### Features

Here are the `cargo` features that you can enable:

* `debug_code_generation`: Prints the code generated by
`#[derive(StateMachineFuture)]` to `stdout` for debugging purposes.

### License

License

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Rust

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 18, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言