10 个版本
使用旧的 Rust 2015
| 0.2.0 | 2018年11月10日 | 
|---|---|
| 0.1.8 | 2018年10月21日 | 
| 0.1.7 | 2018年6月4日 | 
| 0.1.6 | 2018年2月12日 | 
| 0.1.4 | 2017年12月19日 | 
1542 在 过程宏 中排名
596 每月下载量
在 13 个 crate 中使用(通过 state_machine_future)
69KB
 1.5K  SLoC
state_machine_future
轻松地从状态机创建类型安全的 Future — 无需样板代码。
state_machine_future 检查状态机和它们的转换,然后为您生成 Future 实现和类型状态0 样板代码。
简介
大多数时候,使用 Future 组合器如 map 和 then 是描述异步计算的好方法。有时,描述当前过程的最佳方式是状态机。
在 Rust 中编写状态机时,我们希望 利用类型系统来强制只发生有效的状态转换。为了做到这一点,我们希望有 类型状态0:代表状态机中每个状态的类型,以及其签名仅允许有效状态转换的方法。但我们也需要每个可能状态的 enum,以便我们可以将整个状态机作为一个单一实体来处理,并为其实现 Future。但这将产生大量的样板代码...
现在有了 #[derive(StateMachineFuture)]。
使用 #[derive(StateMachineFuture)],我们描述了状态及其之间可能存在的转换,然后自定义的 derive 生成
- 
状态机中每个状态的类型状态。 
- 
实现 Future的整个状态机的类型。
- 
一个具体的 start方法,它为您构建状态机Future,并初始化为起始状态。
- 
一个状态转换轮询特质,对于每个非最终状态 ZeeChoo都有一个poll_zee_choo方法。这个特质描述了状态机的有效转换,其方法由Future::poll调用。
然后,我们只需要实现生成的状态转换轮询特质。
此外,#[derive(StateMachineFuture)] 可以静态地防止在编写状态机时出现的一些陷阱
- 
每个状态都可以从起始状态到达:没有无用的状态。 
- 
没有状态无法达到最终状态。这些状态否则会导致无限循环。 
- 
所有状态转换都是有效的。 由于生成的类型状态,尝试进行无效的状态转换将无法通过类型检查。 
指南
使用 enum 描述状态机的状态,并将其与 #[derive(StateMachineFuture)] 一起添加到其中
#[derive(StateMachineFuture)]
enum MyStateMachine {
    // ...
}
必须有 一个起始 状态,它是构建时的初始状态;一个 就绪 状态,对应于 Future::Item;以及一个 错误 状态,对应于 Future::Error。
#[derive(StateMachineFuture)]
enum MyStateMachine {
    #[state_machine_future(start)]
    Start,
    // ...
    #[state_machine_future(ready)]
    Ready(MyItem),
    #[state_machine_future(error)]
    Error(MyError),
}
enum 的其他变体是中间状态。
我们使用 #[state_machine_future(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),
}
从这个状态机描述中,自定义 derive 为我们生成了样板代码。
对于每个状态,自定义 derive 创建
- 状态的状态类型。类型的名称与变体名称匹配,例如 Intermediate状态变体的类型状态也命名为Intermediate。生成的结构类型与变体类型匹配:单元样式变体产生单元结构,元组样式变体产生元组结构,结构样式变体产生具有字段的正常结构。
| 状态 enum变体 | 生成的状态类型 | 
|---|---|
| 枚举 状态机 {MyState, ... } | 结构体 MyState; | 
| 枚举 状态机 {MyState(布尔类型, 无符号整数类型), ... } | 结构体 MyState(布尔类型, 无符号整数类型); | 
| 枚举 状态机 {MyState{x: 无符号整数类型 }, ... } | 结构体 MyState { x: 无符号整数类型 }; | 
- 一个枚举类型,用于表示该状态之后可能出现的所有状态。此枚举类型命名为 AfterX,其中X是状态的名称。对于可以转换到X的每个Y状态,都有一个From<Y>实现。例如,Intermediate状态将获得:
enum AfterIntermediate {
    Start(Start),
    Ready(Ready),
}
impl From<Start> for AfterIntermediate {
    // ...
}
impl From<Ready> for AfterIntermediate {
    // ...
}
接下来,对于整个状态机,自定义 derive 生成了
- 
一个状态机 Future类型,本质上是一个包含所有不同类型状态的枚举。此类型命名为BlahFuture,其中Blah是状态机描述枚举的名称。在本例中,状态机描述名为MyStateMachine,生成的状态机未来类型将命名为MyStateMachineFuture。
- 
一个轮询特质, PollBordle,其中Bordle是此状态机描述的名称。对于每个非最终状态TootWasabi,此特质有一个方法,poll_toot_wasabi,它类似于Future::poll,但专门针对当前状态。每个方法都通过RentToOwn获取其状态的条件所有权,并返回一个futures::Poll<AfterThisState, Error>,其中Error是状态机的错误类型。此签名 不允许无效的状态转换,这使得尝试非法状态转换无法通过类型检查。以下是一个示例,即MyStateMachine的轮询特质:
trait PollMyStateMachine {
    fn poll_start<'a>(
        start: &'a mut RentToOwn<'a, Start>,
    ) -> Poll<AfterStart, Error>;
    fn poll_intermediate<'a>(
        intermediate: &'a mut RentToOwn<'a, Intermediate>,
    ) -> Poll<AfterIntermediate, Error>;
}
- 
该类型的 Future实现。此实现根据未来所处的状态调用适当的轮询特质方法。- 
如果 Future处于Start状态,则它使用<MyStateMachine as PollMyStateMachine>::poll_start。
- 
如果它处于 Intermediate状态,则它使用<MyStateMachine as PollMyStateMachine>::poll_intermediate。
- 
等等... 
 
- 
- 
为描述类型生成的具体 start方法(例如本例中的MyStateMachine::start),该方法为您构造一个新状态机的Future类型,其处于 start 状态。此方法为 start 状态的每个字段都有一个参数。
| Start enum变体 | 生成的 start方法 | 
|---|---|
| MyStart, | fn start() ->MyStateMachineFuture { ... } | 
| MyStart(布尔类型, 无符号整数类型), | fn start(arg0: 布尔类型, arg1: 无符号整数类型) ->MyStateMachineFuture { ... } | 
| MyStart{x: 字符,y: 布尔类型 }, | fn start(x: 字符, y: 布尔类型) ->MyStateMachineFuture { ... } | 
有了所有这些生成的类型和特质,我们只需为我们的状态机 Blah 实现 impl PollBlah for Blah。
impl PollMyStateMachine for MyStateMachine {
    fn poll_start<'a>(
        start: &'a mut RentToOwn<'a, Start>
    ) -> Poll<AfterStart, MyError> {
        // 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<'a>(
        intermediate: &'a mut RentToOwn<'a, Intermediate>
    ) -> Poll<AfterIntermediate, MyError> {
        // Same deal as above...
    }
}
上下文
状态机还允许传递一个上下文,这个上下文可以在每个 poll_* 方法中使用,无需在每个方法中显式包含它。
可以通过 state_machine_future 属性的 context 参数来指定上下文。这将向 start 方法以及特质的每个 poll_* 方法添加参数。
#[macro_use]
extern crate state_machine_future;
extern crate futures;
use futures::*;
use state_machine_future::*;
struct MyContext {
}
struct MyItem {
}
enum MyError {
}
#[derive(StateMachineFuture)]
#[state_machine_future(context = "MyContext")]
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),
}
impl PollMyStateMachine for MyStateMachine {
    fn poll_start<'s, 'c>(
        start: &'s mut RentToOwn<'s, Start>,
        context: &'c mut RentToOwn<'c, MyContext>
    ) -> Poll<AfterStart, MyError> {
        // The `context` instance passed into `start` is available here.
        // It is a mutable reference, so are free to modify it.
        unimplemented!()
    }
    fn poll_intermediate<'s, 'c>(
        intermediate: &'s mut RentToOwn<'s, Intermediate>,
        context: &'c mut RentToOwn<'c, MyContext>
    ) -> Poll<AfterIntermediate, MyError> {
        // The `context` is available here as well.
        // It is the same instance. This means if `poll_start` modified it, those
        // changes will be visible to this method as well.
        unimplemented!()
    }
}
fn main() {
    let _ = MyStateMachine::start(MyContext { });
}
与状态参数相同,上下文可以通过 RentToOwn 类型来获取!然而,请注意,一旦你获取了上下文,状态机将 总是 返回 Async::NotReady 不 再调用 poll_ 方法。唯一的例外是当状态机处于就绪或错误状态时,如果已经获取了上下文,则在轮询时将正常解析。
就是这样!
示例
以下是一个示例,展示了两个玩家通过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
/// ```
#[derive(StateMachineFuture)]
enum Game {
    /// The game begins with an invitation to play from one player to another.
    ///
    /// Once the invited player accepts the invitation over HTTP, then we will
    /// switch states into playing the game, waiting to recieve each turn.
    #[state_machine_future(start, transitions(WaitingForTurn))]
    Invite {
        invitation: HttpInvitationFuture,
        from: Player,
        to: Player,
    },
    // We are waiting on a turn.
    //
    // Upon receiving it, if the game is now complete, then we go to the
    // `Finished` state. Otherwise, we give the other player a turn.
    #[state_machine_future(transitions(WaitingForTurn, Finished))]
    WaitingForTurn {
        turn: HttpTurnFuture,
        active: Player,
        idle: Player,
    },
    // The game is finished with a `GameResult`.
    //
    // The `GameResult` becomes the `Future::Item`.
    #[state_machine_future(ready)]
    Finished(GameResult),
    // Any state transition can implicitly go to this error state if we get an
    // `HttpError` while waiting on a turn or invitation acceptance.
    //
    // This `HttpError` is used as the `Future::Error`.
    #[state_machine_future(error)]
    Error(HttpError),
}
// Now, we implement the generated state transition polling trait for our state
// machine description type.
impl PollGame for Game {
    fn poll_invite<'a>(
        invite: &'a mut RentToOwn<'a, Invite>
    ) -> Poll<AfterInvite, HttpError> {
        // See if the invitation has been accepted. If not, this will early
        // return with `Ok(Async::NotReady)` or propagate any HTTP errors.
        try_ready!(invite.invitation.poll());
        // We're ready to transition into the `WaitingForTurn` state, so take
        // ownership of the `Invite` and then construct and return the new
        // state.
        let invite = invite.take();
        let waiting = WaitingForTurn {
            turn: invite.from.request_turn(),
            active: invite.from,
            idle: invite.to,
        };
        transition!(waiting)
    }
    fn poll_waiting_for_turn<'a>(
        waiting: &'a mut RentToOwn<'a, WaitingForTurn>
    ) -> Poll<AfterWaitingForTurn, HttpError> {
        // See if the next turn has arrived over HTTP. Again, this will early
        // return `Ok(Async::NotReady)` if the turn hasn't arrived yet, and
        // propagate any HTTP errors that we might encounter.
        let turn = try_ready!(waiting.turn.poll());
        // Ok, we have a new turn. Take ownership of the `WaitingForTurn` state,
        // process the turn and if the game is over, then transition to the
        // `Finished` state, otherwise swap which player we need a new turn from
        // and request the turn over HTTP.
        let waiting = waiting.take();
        if let Some(game_result) = process_turn(turn) {
            transition!(Finished(game_result))
        } else {
            let next_waiting = WaitingForTurn {
                turn: waiting.idle.request_turn(),
                active: waiting.idle,
                idle: waiting.active,
            };
            Ok(Async::Ready(next_waiting.into()))
        }
    }
}
// To spawn a new `Game` as a `Future` on whatever executor we're using (for
// example `tokio`), we use `Game::start` to construct the `Future` in its start
// state and then pass it to the executor.
fn spawn_game(handle: TokioHandle) {
    let from = get_some_player();
    let to = get_another_player();
    let invitation = invite(&from, &to);
    let future = Game::start(invitation, from, to);
    handle.spawn(future)
}
属性
以下是 state_machine_future 所使用的所有属性的列表。
- 
#[derive(StateMachineFuture)]:放置在一个enum上,该枚举描述了一个状态机。
- 
#[state_machine_future(derive(Clone, Debug, ...))]:放置在描述状态机的enum上。此属性描述了要在生成的Future类型上放置哪些#[derive(...)]。
- 
#[state_machine_future(start)]:用于状态机描述枚举的一个变体。必须有恰好一个带有此属性的变体。这描述了初始启动状态。生成的start方法有一个参数,对应于这个变体的每个字段。
- 
#[state_machine_future(ready)]:用于状态机描述的变体enum。必须有且只有一个变体具有此属性。它必须是一个具有一个字段的元组式变体,例如Ready(MyItemType)。生成的Future实现使用字段类型作为Future::Item。
- 
#[state_machine_future(error)]:用于状态机描述的变体enum。必须有且只有一个变体具有此属性。它必须是一个具有一个字段的元组式变体,例如Error(MyError)。生成的Future实现使用字段类型作为Future::Error。
- 
#[state_machine_future(transitions(OtherState, AnotherState, ...))]:用于状态机描述的变体enum。描述了此状态可以转换到的状态。
宏
提供了一个辅助宏,有助于减少状态转换的样板代码。因此,以下代码
Ok(Ready(NextState(1).转换为()))
transition!(NextState(1))
功能
以下是您可以启用的cargo功能
- debug_code_generation:打印由- #[derive(StateMachineFuture)]生成的代码,以进行调试。
许可协议
根据您的选择,许可为以下之一
。
贡献
有关黑客信息,请参阅CONTRIBUTING.md。
除非您明确表示,否则根据 Apache-2.0 许可证定义的您提交的任何贡献,都应如上所述双重许可,不附加任何其他条款或条件。
依赖关系
~3.5MB
~75K SLoC

