{"id":18016895,"url":"https://github.com/fitzgen/state_machine_future","last_synced_at":"2025-03-03T17:04:02.340Z","repository":{"id":54295966,"uuid":"113249493","full_name":"fitzgen/state_machine_future","owner":"fitzgen","description":"Easily create type-safe `Future`s from state machines — without the boilerplate.","archived":false,"fork":false,"pushed_at":"2019-07-11T20:34:28.000Z","size":140,"stargazers_count":330,"open_issues_count":8,"forks_count":22,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-02-24T16:10:27.042Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fitzgen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-12-06T00:42:02.000Z","updated_at":"2025-02-06T13:40:20.000Z","dependencies_parsed_at":"2022-08-13T11:20:10.478Z","dependency_job_id":null,"html_url":"https://github.com/fitzgen/state_machine_future","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fstate_machine_future","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fstate_machine_future/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fstate_machine_future/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitzgen%2Fstate_machine_future/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fitzgen","download_url":"https://codeload.github.com/fitzgen/state_machine_future/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241699273,"owners_count":20005363,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-30T04:19:37.865Z","updated_at":"2025-03-03T17:04:02.316Z","avatar_url":"https://github.com/fitzgen.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `state_machine_future`\n\n[![](https://docs.rs/state_machine_future/badge.svg)](https://docs.rs/state_machine_future/) [![](https://img.shields.io/crates/v/state_machine_future.svg)](https://crates.io/crates/state_machine_future) [![](https://img.shields.io/crates/d/state_machine_future.png)](https://crates.io/crates/state_machine_future) [![Build Status](https://travis-ci.org/fitzgen/state_machine_future.png?branch=master)](https://travis-ci.org/fitzgen/state_machine_future)\n\nEasily create type-safe `Future`s from state machines — without the boilerplate.\n\n`state_machine_future` type checks state machines and their state transitions,\nand then generates `Future` implementations and typestate\u003csup\u003e[0][]\u003c/sup\u003e\nboilerplate for you.\n\n* [Introduction](#introduction)\n* [Guide](#guide)\n* [Example](#example)\n* [Attributes](#attributes)\n* [Macro](#macro)\n* [Features](#features)\n* [License](#license)\n* [Contribution](#contribution)\n\n### Introduction\n\nMost of the time, using `Future` combinators like `map` and `then` are a great\nway to describe an asynchronous computation. Other times, the most natural way\nto describe the process at hand is a state machine.\n\nWhen writing state machines in Rust, we want to *leverage the type system to\nenforce that only valid state transitions may occur*. To do that, we want\n*typestates*\u003csup\u003e[0][]\u003c/sup\u003e: types that represents each state in the state\nmachine, and methods whose signatures only permit valid state transitions. But\nwe *also* need an `enum` of every possible state, so we can treat the whole\nstate machine as a single entity, and implement `Future` for it. But this is\ngetting to be a *lot* of boilerplate...\n\nEnter `#[derive(StateMachineFuture)]`.\n\nWith `#[derive(StateMachineFuture)]`, we describe the states and the possible\ntransitions between them, and then the custom derive generates:\n\n* A typestate for each state in the state machine.\n\n* A type for the whole state machine that implements `Future`.\n\n* A concrete `start` method that constructs the state machine `Future` for you,\ninitialized to its start state.\n\n* A state transition polling trait, with a `poll_zee_choo` method for each\nnon-final state `ZeeChoo`. This trait describes the state machine's valid\ntransitions, and its methods are called by `Future::poll`.\n\nThen, all *we* need to do is implement the generated state transition polling\ntrait.\n\nAdditionally, `#[derive(StateMachineFuture)]` will statically prevent against\nsome footguns that can arise when writing state machines:\n\n* Every state is reachable from the start state: *there are no useless states.*\n\n* *There are no states which cannot reach a final state*. These states would\notherwise lead to infinite loops.\n\n* *All state transitions are valid.* Attempting to make an invalid state\ntransition fails to type check, thanks to the generated typestates.\n\n### Guide\n\nDescribe the state machine's states with an `enum` and add\n`#[derive(StateMachineFuture)]` to it:\n\n```rust\n#[derive(StateMachineFuture)]\nenum MyStateMachine {\n    // ...\n}\n```\n\nThere must be one **start** state, which is the initial state upon construction;\none **ready** state, which corresponds to `Future::Item`; and one **error**\nstate, which corresponds to `Future::Error`.\n\n```rust\n#[derive(StateMachineFuture)]\nenum MyStateMachine {\n    #[state_machine_future(start)]\n    Start,\n\n    // ...\n\n    #[state_machine_future(ready)]\n    Ready(MyItem),\n\n    #[state_machine_future(error)]\n    Error(MyError),\n}\n```\n\nAny other variants of the `enum` are intermediate states.\n\nWe define which state-to-state transitions are valid with\n`#[state_machine_future(transitions(...))]`. This attribute annotates a state\nvariant, and lists which other states can be transitioned to immediately after\nthis state.\n\nA final state (either **ready** or **error**) must be reachable from every\nintermediate state and the **start** state. Final states are not allowed to have\ntransitions.\n\n```rust\n#[derive(StateMachineFuture)]\nenum MyStateMachine {\n    #[state_machine_future(start, transitions(Intermediate))]\n    Start,\n\n    #[state_machine_future(transitions(Start, Ready))]\n    Intermediate { x: usize, y: usize },\n\n    #[state_machine_future(ready)]\n    Ready(MyItem),\n\n    #[state_machine_future(error)]\n    Error(MyError),\n}\n```\n\nFrom this state machine description, the custom derive generates boilerplate for\nus.\n\nFor each state, the custom derive creates:\n\n* A typestate for the state. The type's name matches the variant name, for\nexample the `Intermediate` state variant's typestate is also named `Intermediate`.\nThe kind of struct type generated matches the variant kind: a unit-style variant\nresults in a unit struct, a tuple-style variant results in a tuple struct, and a\nstruct-style variant results in a normal struct with fields.\n\n| State `enum` Variant                              | Generated Typestate            |\n| ------------------------------------------------- | ------------------------------ |\n| `enum StateMachine { MyState, ... }`              | `struct MyState;`              |\n| `enum StateMachine { MyState(bool, usize), ... }` | `struct MyState(bool, usize);` |\n| `enum StateMachine { MyState { x: usize }, ... }` | `struct MyState { x: usize };` |\n\n* An `enum` for the possible states that can come after this state. This `enum`\nis named `AfterX` where `X` is the state's name. There is also a `From\u003cY\u003e`\nimplementation for each `Y` state that can be transitioned to after `X`. For\nexample, the `Intermediate` state would get:\n\n```rust\nenum AfterIntermediate {\n    Start(Start),\n    Ready(Ready),\n}\n\nimpl From\u003cStart\u003e for AfterIntermediate {\n    // ...\n}\n\nimpl From\u003cReady\u003e for AfterIntermediate {\n    // ...\n}\n```\n\nNext, for the state machine as a whole, the custom derive generates:\n\n* A state machine `Future` type, which is essentially an `enum` of all the\ndifferent typestates. This type is named `BlahFuture` where `Blah` is the name\nof the state machine description `enum`. In this example, where the state\nmachine description is named `MyStateMachine`, the generated state machine\nfuture type would be named `MyStateMachineFuture`.\n\n* A polling trait, `PollBordle` where `Bordle` is this state machine\ndescription's name. For each non-final state `TootWasabi`, this trait has a\nmethod, `poll_toot_wasabi`, which is like `Future::poll` but specialized to the\ncurrent state. Each method takes conditional ownership of its state (via\n[`RentToOwn`][rent_to_own]) and returns a `futures::Poll\u003cAfterThisState, Error\u003e`\nwhere `Error` is the state machine's error type. This signature *does not allow\ninvalid state transitions*, which makes attempting an illegal state transition\nfail to type check. Here is the `MyStateMachine`'s polling trait, for example:\n\n```rust\ntrait PollMyStateMachine {\n    fn poll_start\u003c'a\u003e(\n        start: \u0026'a mut RentToOwn\u003c'a, Start\u003e,\n    ) -\u003e Poll\u003cAfterStart, Error\u003e;\n\n    fn poll_intermediate\u003c'a\u003e(\n        intermediate: \u0026'a mut RentToOwn\u003c'a, Intermediate\u003e,\n    ) -\u003e Poll\u003cAfterIntermediate, Error\u003e;\n}\n```\n\n* An implementation of `Future` for that type. This implementation dispatches to\nthe appropriate polling trait method depending on what state the future is\nin:\n\n  * If the `Future` is in the `Start` state, then it uses `\u003cMyStateMachine as\n    PollMyStateMachine\u003e::poll_start`.\n\n  * If it is in the `Intermediate` state, then it uses `\u003cMyStateMachine as\n    PollMyStateMachine\u003e::poll_intermediate`.\n\n  * Etc...\n\n* A concrete `start` method for the description type (so `MyStateMachine::start`\nin this example) which constructs a new state machine `Future` type in its\n**start** state for you. This method has a parameter for each field in the\n**start** state variant.\n\n| Start `enum` Variant            | Generated `start` Method                                            |\n| ------------------------------- | ------------------------------------------------------------------- |\n| `MyStart,`                      | `fn start() -\u003e MyStateMachineFuture { ... }`                        |\n| `MyStart(bool, usize),`         | `fn start(arg0: bool, arg1: usize) -\u003e MyStateMachineFuture { ... }` |\n| `MyStart { x: char, y: bool },` | `fn start(x: char, y: bool) -\u003e MyStateMachineFuture { ... }`        |\n\nGiven all those generated types and traits, all we have to do is `impl PollBlah\nfor Blah` for our state machine `Blah`.\n\n```rust\nimpl PollMyStateMachine for MyStateMachine {\n    fn poll_start\u003c'a\u003e(\n        start: \u0026'a mut RentToOwn\u003c'a, Start\u003e\n    ) -\u003e Poll\u003cAfterStart, MyError\u003e {\n        // Call `try_ready!(start.inner.poll())` with any inner futures here.\n        //\n        // If we're ready to transition states, then we should return\n        // `Ok(Async::Ready(AfterStart))`. If we are not ready to transition\n        // states, return `Ok(Async::NotReady)`. If we encounter an error,\n        // return `Err(...)`.\n    }\n\n    fn poll_intermediate\u003c'a\u003e(\n        intermediate: \u0026'a mut RentToOwn\u003c'a, Intermediate\u003e\n    ) -\u003e Poll\u003cAfterIntermediate, MyError\u003e {\n        // Same deal as above...\n    }\n}\n```\n\n### Context\n\nThe state machine also allows to pass in a context that is available in every `poll_*` method\nwithout having to explicitly include it in every one.\n\nThe context can be specified through the `context` argument of the `state_machine_future` attribute.\nThis will add parameters to the `start` method as well as to each `poll_*` method of the trait.\n\n```rust\n#[macro_use]\nextern crate state_machine_future;\nextern crate futures;\n\nuse futures::*;\nuse state_machine_future::*;\n\nstruct MyContext {\n\n}\n\nstruct MyItem {\n\n}\n\nenum MyError {\n\n}\n\n#[derive(StateMachineFuture)]\n#[state_machine_future(context = \"MyContext\")]\nenum MyStateMachine {\n    #[state_machine_future(start, transitions(Intermediate))]\n    Start,\n\n    #[state_machine_future(transitions(Start, Ready))]\n    Intermediate { x: usize, y: usize },\n\n    #[state_machine_future(ready)]\n    Ready(MyItem),\n\n    #[state_machine_future(error)]\n    Error(MyError),\n}\n\nimpl PollMyStateMachine for MyStateMachine {\n    fn poll_start\u003c's, 'c\u003e(\n        start: \u0026's mut RentToOwn\u003c's, Start\u003e,\n        context: \u0026'c mut RentToOwn\u003c'c, MyContext\u003e\n    ) -\u003e Poll\u003cAfterStart, MyError\u003e {\n\n        // The `context` instance passed into `start` is available here.\n        // It is a mutable reference, so are free to modify it.\n\n        unimplemented!()\n    }\n\n    fn poll_intermediate\u003c's, 'c\u003e(\n        intermediate: \u0026's mut RentToOwn\u003c's, Intermediate\u003e,\n        context: \u0026'c mut RentToOwn\u003c'c, MyContext\u003e\n    ) -\u003e Poll\u003cAfterIntermediate, MyError\u003e {\n\n        // The `context` is available here as well.\n        // It is the same instance. This means if `poll_start` modified it, those\n        // changes will be visible to this method as well.\n\n        unimplemented!()\n    }\n}\n\nfn main() {\n    let _ = MyStateMachine::start(MyContext { });\n}\n```\n\nSame as for the state argument, the context can be taken through the `RentToOwn` type!\nHowever, be aware that once you take the context, the state machine will **always** return\n`Async::NotReady` **without** invoking the `poll_` methods anymore. The one exception to\nthis is when the state machine is in a ready or error state, where it will resolve normally\nwhen polled if the context has been taken.\n\nThat's it!\n\n### Example\n\nHere is an example of a simple turn-based game played by two players over HTTP.\n\n```rust\n#[macro_use]\nextern crate state_machine_future;\n\n#[macro_use]\nextern crate futures;\n\nuse futures::{Async, Future, Poll};\nuse state_machine_future::RentToOwn;\n\n/// The result of a game.\npub struct GameResult {\n    winner: Player,\n    loser: Player,\n}\n\n/// Some kind of simple turn based game.\n///\n/// ```text\n///              Invite\n///                |\n///                |\n///                | accept invitation\n///                |\n///                |\n///                V\n///           WaitingForTurn --------+\n///                |   ^             |\n///                |   |             | receive turn\n///                |   |             |\n///                |   +-------------+\n/// game concludes |\n///                |\n///                |\n///                |\n///                V\n///            Finished\n/// ```\n#[derive(StateMachineFuture)]\nenum Game {\n    /// The game begins with an invitation to play from one player to another.\n    ///\n    /// Once the invited player accepts the invitation over HTTP, then we will\n    /// switch states into playing the game, waiting to recieve each turn.\n    #[state_machine_future(start, transitions(WaitingForTurn))]\n    Invite {\n        invitation: HttpInvitationFuture,\n        from: Player,\n        to: Player,\n    },\n\n    // We are waiting on a turn.\n    //\n    // Upon receiving it, if the game is now complete, then we go to the\n    // `Finished` state. Otherwise, we give the other player a turn.\n    #[state_machine_future(transitions(WaitingForTurn, Finished))]\n    WaitingForTurn {\n        turn: HttpTurnFuture,\n        active: Player,\n        idle: Player,\n    },\n\n    // The game is finished with a `GameResult`.\n    //\n    // The `GameResult` becomes the `Future::Item`.\n    #[state_machine_future(ready)]\n    Finished(GameResult),\n\n    // Any state transition can implicitly go to this error state if we get an\n    // `HttpError` while waiting on a turn or invitation acceptance.\n    //\n    // This `HttpError` is used as the `Future::Error`.\n    #[state_machine_future(error)]\n    Error(HttpError),\n}\n\n// Now, we implement the generated state transition polling trait for our state\n// machine description type.\n\nimpl PollGame for Game {\n    fn poll_invite\u003c'a\u003e(\n        invite: \u0026'a mut RentToOwn\u003c'a, Invite\u003e\n    ) -\u003e Poll\u003cAfterInvite, HttpError\u003e {\n        // See if the invitation has been accepted. If not, this will early\n        // return with `Ok(Async::NotReady)` or propagate any HTTP errors.\n        try_ready!(invite.invitation.poll());\n\n        // We're ready to transition into the `WaitingForTurn` state, so take\n        // ownership of the `Invite` and then construct and return the new\n        // state.\n        let invite = invite.take();\n        let waiting = WaitingForTurn {\n            turn: invite.from.request_turn(),\n            active: invite.from,\n            idle: invite.to,\n        };\n        transition!(waiting)\n    }\n\n    fn poll_waiting_for_turn\u003c'a\u003e(\n        waiting: \u0026'a mut RentToOwn\u003c'a, WaitingForTurn\u003e\n    ) -\u003e Poll\u003cAfterWaitingForTurn, HttpError\u003e {\n        // See if the next turn has arrived over HTTP. Again, this will early\n        // return `Ok(Async::NotReady)` if the turn hasn't arrived yet, and\n        // propagate any HTTP errors that we might encounter.\n        let turn = try_ready!(waiting.turn.poll());\n\n        // Ok, we have a new turn. Take ownership of the `WaitingForTurn` state,\n        // process the turn and if the game is over, then transition to the\n        // `Finished` state, otherwise swap which player we need a new turn from\n        // and request the turn over HTTP.\n        let waiting = waiting.take();\n        if let Some(game_result) = process_turn(turn) {\n            transition!(Finished(game_result))\n        } else {\n            let next_waiting = WaitingForTurn {\n                turn: waiting.idle.request_turn(),\n                active: waiting.idle,\n                idle: waiting.active,\n            };\n            Ok(Async::Ready(next_waiting.into()))\n        }\n    }\n}\n\n// To spawn a new `Game` as a `Future` on whatever executor we're using (for\n// example `tokio`), we use `Game::start` to construct the `Future` in its start\n// state and then pass it to the executor.\nfn spawn_game(handle: TokioHandle) {\n    let from = get_some_player();\n    let to = get_another_player();\n    let invitation = invite(\u0026from, \u0026to);\n    let future = Game::start(invitation, from, to);\n    handle.spawn(future)\n}\n```\n\n### Attributes\n\nThis is a list of all of the attributes used by `state_machine_future`:\n\n* `#[derive(StateMachineFuture)]`: Placed on an `enum` that describes a state\nmachine.\n\n* `#[state_machine_future(derive(Clone, Debug, ...))]`: Placed on the `enum`\nthat describes the state machine. This attribute describes which\n`#[derive(...)]`s to place on the generated `Future` type.\n\n* `#[state_machine_future(start)]`: Used on a variant of the state machine\ndescription `enum`. There must be exactly one variant with this attribute. This\ndescribes the initial starting state. The generated `start` method has a\nparameter for each field in this variant.\n\n* `#[state_machine_future(ready)]`: Used on a variant of the state machine\ndescription `enum`. There must be exactly one variant with this attribute. It\nmust be a tuple-style variant with one field, for example `Ready(MyItemType)`.\nThe generated `Future` implementation uses the field's type as `Future::Item`.\n\n* `#[state_machine_future(error)]`: Used on a variant of the state machine\ndescription `enum`. There must be exactly one variant with this attribute. It\nmust be a tuple-style variant with one field, for example `Error(MyError)`.  The\ngenerated `Future` implementation uses the field's type as `Future::Error`.\n\n* `#[state_machine_future(transitions(OtherState, AnotherState, ...))]`: Used on\na variant of the state machine description `enum`. Describes the states that\nthis one can transition to.\n\n### Macro\n\nAn auxiliary macro is provided that helps reducing boilerplate code for state\ntransitions. So, the following code:\n\n```Ok(Ready(NextState(1).into()))```\n\nCan be reduced to:\n\n```transition!(NextState(1))```\n\n### Features\n\nHere are the `cargo` features that you can enable:\n\n* `debug_code_generation`: Prints the code generated by\n`#[derive(StateMachineFuture)]` to `stdout` for debugging purposes.\n\n### License\n\nLicensed under either of\n\n * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\n * [MIT license](http://opensource.org/licenses/MIT)\n\nat your option.\n\n### Contribution\n\nSee\n[CONTRIBUTING.md](https://github.com/fitzgen/state_machine_future/blob/master/CONTRIBUTING.md)\nfor hacking.\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall be\ndual licensed as above, without any additional terms or conditions.\n\n\n[0]: https://en.wikipedia.org/wiki/Typestate_analysis\n\n[rent_to_own]: https://crates.io/crates/rent_to_own\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitzgen%2Fstate_machine_future","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffitzgen%2Fstate_machine_future","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitzgen%2Fstate_machine_future/lists"}