24 个版本 (6 个重大更新)

0.9.0-rc22021 年 9 月 2 日
0.8.0 2021 年 7 月 16 日
0.1.3-alpha2020 年 11 月 27 日

951Rust 模式

每月 37 次下载
3 个包(2 个直接)中使用

MIT/Apache

300KB
182

#[typestate]

github

此库为开发人员提供宏来设计类型状态对象。

[dependencies]
typestate = "0.9.0-rc2"

编译器支持:需要 rustc 1.42+

简介

你在 Java 中对 IllegalStateException 感到沮丧吗?

类型状态允许你为对象定义 安全 的使用协议。编译器将帮助你在旅途中,并禁止给定状态上的错误。你将无法再尝试从关闭的流中读取。

#[typestate] 基于 state_machine_future 包的思想。

Rust 中的类型状态

类型状态并不是 Rust 的新概念。关于这个主题有多个博客文章[1, 2, 3],以及《嵌入式 Rust 书》中的章节

简而言之,我们可以手动编写类型状态,添加一些泛型,将其声明为 "状态",然后我们可以继续使用我们新的状态机。

然而,这种方法是 容易出错冗长 的(特别是对于更大的自动机)。它还提供了 没有 关于自动机的保证,除非当然,你之前设计和测试了设计。

作为程序员,我们想要自动化这项繁琐的工作,为此,我们使用 Rust 的强大过程宏!

基本指南

假设我们被要求构建交通灯的固件,我们可以将其打开和关闭,并在绿色、黄色和红色之间循环。

我们首先使用 #[typestate] 宏声明一个模块。

#[typestate]
mod traffic_light {}

当然,这不会做任何事情,事实上它会导致一个错误,提示我们没有声明一个自动机

因此,我们的下一个任务是完成这个任务。在traffic_light模块内部,我们声明一个结构体,并使用#[automaton]进行标注。

#[automaton]
pub struct TrafficLight;

我们的下一步是声明状态。我们声明了三个空的结构体,并使用"[state]进行标注。

#[state] pub struct Green;
#[state] pub struct Yellow;
#[state] pub struct Red;

到目前为止,一切顺利,然而应该会出现一些错误,关于缺少初始和最终状态。

要声明初始和最终状态,我们需要将它们视为可以通过转换来描述的。每当创建一个对象时,创建它的方法将对象置于初始状态。同样,每当一个方法消耗一个对象并且不返回它(或它的类似版本),它就会使对象达到最终状态。

有了这个想法,我们可以制定以下规则

  • 不接收有效状态(即self)并返回有效状态的函数,描述了一个初始状态。
  • 接收有效状态(即self)但不返回有效状态的函数,描述了一个最终状态。

因此,我们编写以下函数签名

fn turn_on() -> Red;
fn turn_off(self);

然而,这些是自由函数,意味着self与任何东西无关。为了将它们附加到状态,我们用名为它们应该附加到的状态的特质将它们包装起来。因此,我们之前的示例变为

trait Red {
    fn turn_on() -> Red;
    fn turn_off(self);
}

在继续之前,快速回顾一下

  • 模块使用#[typestate]进行标注,从而启用领域特定语言。
  • 要声明主自动机,我们将#[automaton]附加到一个结构体上。
  • 状态通过附加#[state]进行声明。
  • 状态函数通过具有相同名称的特质声明。
  • 初始和最终状态通过具有“特殊”签名的函数声明。

最后,我们需要解决状态之间如何相互转换的问题。一个敏锐的读者可能会推断,我们可以消费一个状态并返回另一个状态,这样的读者将完全正确。

例如,要在Red状态和Green状态之间进行转换,我们这样做

trait Red {
    fn to_green(self) -> Green;
}

在此基础上,我们可以完成其他状态的转换

pub trait Green {
    fn to_yellow(self) -> Yellow;
}

pub trait Yellow {
    fn to_red(self) -> Red;
}

pub trait Red {
    fn to_green(self) -> Green;
    fn turn_on() -> Red;
    fn turn_off(self);
}

完整的代码如下

#[typestate]
mod traffic_light {
    #[automaton]
    pub struct TrafficLight {
        pub cycles: u64,
    }

    #[state] pub struct Green;
    #[state] pub struct Yellow;
    #[state] pub struct Red;

    pub trait Green {
        fn to_yellow(self) -> Yellow;
    }

    pub trait Yellow {
        fn to_red(self) -> Red;
    }

    pub trait Red {
        fn to_green(self) -> Green;
        fn turn_on() -> Red;
        fn turn_off(self);
    }
}

上面的代码将生成

  • 将主结构体扩展为具有state: State字段。
  • 一个密封特质,不允许从外部添加状态。
  • 为每个状态提供描述性函数的特质。

高级指南

当描述类型状态时,有一些功能可能会有所帮助。有两个主要功能尚未讨论。

自转换函数

简单来说,状态可能需要在不转换的情况下自我突变,或者我们可能需要一个简单的获取器。为了声明这些目的的方法,我们可以使用接收self引用(可变或不可变)的函数。

考虑以下示例,其中我们有一个可以立或不立的标志。我们有两个函数,一个检查标志是否立,另一个设置标志立。

#[state] struct Flag {
    up: bool
}

impl Flag {
    fn is_up(&self) -> bool;
    fn set_up(&mut self);
}

由于这些函数没有改变类型状态状态,它们将转换回当前状态。

非确定性转换

考虑一个状态类型依赖于可能失败的外部组件,为了建模这种情况,可以使用 Result<T>。然而,我们需要让我们的状态类型在已知状态之间进行转换,因此我们需要声明两件事情。

  • 声明一个与其它状态并列的 Error 状态。
  • 声明一个 enum 来表示状态的分支。
#[state] struct Error {
    message: String
}

enum OperationResult {
    State, Error
}

在枚举中只能有其他有效状态,并且只支持 Unit 风格的变体。

属性

以下是可用于与 #[typestate] 一起使用的属性列表。

  • #[typestate]:主要的属性宏,没有属性参数。
  • #[typestate(enumerate = "...")]:这个选项让宏生成一个额外的 enum,该枚举允许以“泛型”到状态的方式处理变量和结构。
    • 参数可以声明为带有或没有字符串字面量,如果声明为带有字符串,则该字符串将用作 enum 的标识符。
    • 如果参数使用空字符串或没有字符串,则默认行为是在字符串前添加一个 E
  • #[typestate(state_constructors = "..."):这个选项为具有字段的州生成基本构造函数。

功能

您可以启用的 cargo 功能

  • mermaid-docs 将生成嵌入在您的文档中的 Mermaid.js 状态图。此功能默认启用。
  • export-dot 将生成您的状态机的 .dot 文件。
    • 此功能可以通过以下环境变量进行自定义(来自 DOT 文档
      • DOT_PAD - 指定在图形绘制区域周围延伸多少英寸,以扩展所需的最小绘制区域。
      • DOT_NODESEP - 在 dot 中,nodesep 指定相同等级中相邻节点之间的最小空间,以英寸为单位。
      • DOT_RANKSEP - 在 dot 中,设置所需的等级分离,以英寸为单位。
      • EXPORT_FOLDER - 声明导出文件的目标文件夹。
  • export-plantuml 将生成您的状态机的 PlantUML 状态图(.uml 文件)。
    • 此功能可以通过以下环境变量进行自定义(来自 PlantUML Hitchhiker's Guide
      • PLANTUML_NODESEP - nodesep 指定了同一层中相邻节点之间的最小空间。
      • PLANTUML_RANKSEP - 设置所需的层间距。
      • EXPORT_FOLDER - 声明导出文件的目标文件夹。

类型状态可视化

灯泡 智能灯泡
DOT examples/light_bulb.rs examples/smart_bulb.rs
PlantUML examples/light_bulb.rs examples/smart_bulb.rs

出版物

依赖关系

~1.5–2.2MB
~51K SLoC