4 个版本 (重大变更)
使用旧的Rust 2015
0.3.0 | 2019年6月14日 |
---|---|
0.2.0 | 2019年1月29日 |
0.1.0 | 2015年6月29日 |
0.0.1 | 2015年3月27日 |
#1502 in 过程宏
每月276次下载
用于 miniconf_mqtt
38KB
739 行
Machine
功能
此crate定义了三个过程宏,以帮助您编写基于枚举的状态机,无需编写关联的样板代码。
- 将状态机定义为枚举,每个变体可以包含成员
- 自动添加错误状态,用于无效转换
- 如果需要,转换可以有多个结束状态(根据消息内容等条件)
- 可以生成对状态成员的访问器
- 在父枚举上生成包装方法和访问器
- 生成的代码也写入
target/machine
目录,以便进一步检查 - 在
target/machine
目录中写入点文件,用于生成图形
使用方法
machine可在 crates.io 上获取,并且可以像这样包含到您的Cargo项目中
[dependencies]
machine = "^0.2"
然后像这样将其包含到您的代码中
#[macro_use]
extern crate machine;
示例:交通灯
我们将定义一个表示交通灯的状态机,指定在绿灯状态下允许通过的最大车辆数。
以下机器定义
machine!(
enum Traffic {
Green { count: u8 },
Orange,
Red
}
);
将生成以下代码
#[derive(Clone, Debug, PartialEq)]
pub enum Traffic {
Error,
Green(Green),
Orange(Orange),
Red(Red),
}
#[derive(Clone, Debug, PartialEq)]
pub struct Green {
count: u8,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Orange {}
#[derive(Clone, Debug, PartialEq)]
pub struct Red {}
impl Traffic {
pub fn green(count: u8) -> Traffic {
Traffic::Green(Green { count })
}
pub fn orange() -> Traffic {
Traffic::Orange(Orange {})
}
pub fn red() -> Traffic {
Traffic::Red(Red {})
}
pub fn error() -> Traffic {
Traffic::Error
}
}
转换
从那里,我们可以定义 Advance
消息以转到下一个颜色,以及相关的转换
#[derive(Clone,Debug,PartialEq)]
pub struct Advance;
transitions!(Traffic,
[
(Green, Advance) => Orange,
(Orange, Advance) => Red,
(Red, Advance) => Green
]
);
这将生成一个包含该状态机消息的枚举,以及在父枚举上的 on_advance
方法。
#[derive(Clone, Debug, PartialEq)]
pub enum TrafficMessages {
Advance(Advance),
}
impl Traffic {
pub fn on_advance(self, input: Advance) -> Traffic {
match self {
Traffic::Green(state) => Traffic::Orange(state.on_advance(input)),
Traffic::Orange(state) => Traffic::Red(state.on_advance(input)),
Traffic::Red(state) => Traffic::Green(state.on_advance(input)),
_ => Traffic::Error,
}
}
}
编译器会抱怨在 Green
、Orange
和 Red
结构中缺少 on_advance
。
error[E0599]: no method named `on_advance` found for type `Green` in the current scope
--> tests/t.rs:18:1
|
4 | / machine!(
5 | | enum Traffic {
6 | | Green { count: u8 },
7 | | Orange,
8 | | Red,
9 | | }
10 | | );
| |__- method `on_advance` not found for this
...
18 | / transitions!(Traffic,
19 | | [
20 | | (Green, Advance) => Orange,
21 | | (Orange, Advance) => Red,
22 | | (Red, Advance) => Green
23 | | ]
24 | | );
| |__^
[...]
transitions
宏处理了样板代码,编写包装方法,并确保状态机接收错误消息时将进入错误状态。但我们仍需要手动为每个状态定义转换函数,因为大部分工作将在那里完成
impl Green {
pub fn on_advance(self, _: Advance) -> Orange {
Orange {}
}
}
impl Orange {
pub fn on_advance(self, _: Advance) -> Red {
Red {}
}
}
impl Red {
pub fn on_advance(self, _: Advance) -> Green {
Green {
count: 0
}
}
}
现在我们希望在绿灯状态下添加一个计数通过车辆的消息,并且当至少有10辆车通过时切换到黄灯状态。因此,PassCar
消息只被绿灯状态接受,并且转换有两个可能的最终状态,即绿灯和黄灯。虽然我们可能希望有一个干净的状态机,其中每个状态和消息组合只有一个最终状态,但我们可能会有根据消息值或状态成员的条件,而不需要创建新的状态或消息。
#[derive(Clone,Debug,PartialEq)]
pub struct PassCar { count: u8 }
transitions!(Traffic,
[
(Green, Advance) => Orange,
(Orange, Advance) => Red,
(Red, Advance) => Green,
(Green, PassCar) => [Green, Orange]
]
);
impl Green {
pub fn on_pass_car(self, input: PassCar) -> Traffic {
let count = self.count + input.count;
if count >= 10 {
println!("reached max cars count: {}", count);
Traffic::orange()
} else {
Traffic::green(count)
}
}
}
on_pass_car
方法可以有多个最终状态,因此它必须返回一个 Traffic
。
现在生成的代码将包含一个 on_pass_car
方法,用于 Traffic
枚举。请注意,如果除了 Green
之外的状态接收到 PassCar
消息,状态机将进入 Error
状态并无限期地停留在该状态。
#[derive(Clone, Debug, PartialEq)]
pub enum TrafficMessages {
Advance(Advance),
PassCar(PassCar),
}
impl Traffic {
pub fn on_advance(self, input: Advance) -> Traffic {
match self {
Traffic::Green(state) => Traffic::Orange(state.on_advance(input)),
Traffic::Orange(state) => Traffic::Red(state.on_advance(input)),
Traffic::Red(state) => Traffic::Green(state.on_advance(input)),
_ => Traffic::Error,
}
}
pub fn on_pass_car(self, input: PassCar) -> Traffic {
match self {
Traffic::Green(state) => state.on_pass_car(input),
_ => Traffic::Error,
}
}
}
完整的生成代码可以在 target/machine/traffic.rs
中找到。
机器包还会生成用于 graphviz 的 target/machine/traffic.dot
文件。
digraph Traffic {
Green -> Orange [ label = "Advance" ];
Orange -> Red [ label = "Advance" ];
Red -> Green [ label = "Advance" ];
Green -> Green [ label = "PassCar" ];
Green -> Orange [ label = "PassCar" ];
}
dot -Tpng target/machine/traffic.dot > traffic.png
将生成以下图像
然后我们可以使用消息来触发转换
// starting in green state, no cars have passed
let mut t = Traffic::Green(Green { count: 0 });
t = t.on_pass_car(PassCar { count: 1});
t = t.on_pass_car(PassCar { count: 2});
// still in green state, 3 cars have passed
assert_eq!(t, Traffic::green(3));
// each advance call will move to the next color
t = t.on_advance(Advance);
assert_eq!(t, Traffic::orange());
t = t.on_advance(Advance);
assert_eq!(t, Traffic::red());
t = t.on_advance(Advance);
assert_eq!(t, Traffic::green(0));
t = t.on_pass_car(PassCar { count: 5 });
assert_eq!(t, Traffic::green(5));
// when more than 10 cars have passed, go to the orange state
t = t.on_pass_car(PassCar { count: 7 });
assert_eq!(t, Traffic::orange());
t = t.on_advance(Advance);
assert_eq!(t, Traffic::red());
// if we try to use the PassCar message on state other than Green,
// we go into the error state
t = t.on_pass_car(PassCar { count: 7 });
assert_eq!(t, Traffic::error());
// once in the error state, we stay in the error state
t = t.on_advance(Advance);
assert_eq!(t, Traffic::error());
方法
methods!
过程宏可以生成用于状态成员访问器的包装方法,或者要求在状态上实现方法
methods!(Traffic,
[
Green => get count: u8,
Green => set count: u8,
Green, Orange, Red => fn can_pass(&self) -> bool
]
);
这将生成
- 一个用于
Green
状态的count()
获取器(get
)和包装枚举 - 一个用于
Green
状态的count_mut()
设置器(set
)和包装枚举 - 一个用于包装枚举的
can_pass()
方法,要求所有状态都实现该方法
方法可以有参数,并且这些参数将按照预期传递给状态的相应方法。
impl Orange {}
impl Red {}
impl Green {
pub fn count(&self) -> &u8 {
&self.count
}
pub fn count_mut(&mut self) -> &mut u8 {
&mut self.count
}
}
impl Traffic {
pub fn count(&self) -> Option<&u8> {
match self {
Traffic::Green(ref v) => Some(v.count()),
_ => None,
}
}
pub fn count_mut(&mut self) -> Option<&mut u8> {
match self {
Traffic::Green(ref mut v) => Some(v.count_mut()),
_ => None,
}
}
pub fn can_pass(&self) -> Option<bool> {
match self {
Traffic::Green(ref v) => Some(v.can_pass()),
Traffic::Orange(ref v) => Some(v.can_pass()),
Traffic::Red(ref v) => Some(v.can_pass()),
_ => None,
}
}
}
现在我们可以添加剩余的方法,并得到一个工作的状态机
impl Green {
pub fn can_pass(&self) -> bool {
true
}
}
impl Orange {
pub fn can_pass(&self) -> bool {
false
}
}
impl Red {
pub fn can_pass(&self) -> bool {
false
}
}
许可证
在以下任一许可下授权
- Apache License,版本 2.0 (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任由您选择。
贡献
除非您明确声明,否则您提交的任何有意包含在作品中的贡献,根据 Apache-2.0 许可证的定义,将如上所述双许可,没有任何附加条款或条件。
依赖关系
~2MB
~48K SLoC