Rust有限状态机(FSM)库rust-fsm-dsl的使用,DSL驱动的高效状态机建模与实现
Rust有限状态机(FSM)库rust-fsm-dsl的使用,DSL驱动的高效状态机建模与实现
rust-fsm
crate提供了一个简单通用的框架,用于在Rust中构建状态机。它的核心是StateMachineImpl
trait,允许开发者严格定义状态机的以下方面:
- 输入字母表 - 状态机接收的输入集合
- 可能的状态 - 状态机可能处于的状态集合
- 输出字母表 - 状态机工作的输出结果集合
- 转移函数 - 基于当前状态和输入改变状态的函数
- 输出函数 - 基于当前状态和输入产生输出的函数
- 初始状态
使用DSL定义状态机
DSL通过state_machine
宏解析。以下是一个断路器状态机的示例:
use rust_fsm::*;
state_machine! {
#[derive(Debug)]
#[repr(C)]
/// 一个断路器状态机
circuit_breaker(Closed)
Closed(Unsuccessful) => Open [SetupTimer],
Open(TimerTriggered) => HalfOpen,
HalfOpen => {
Successful => Closed,
Unsuccessful => Open [SetupTimer]
}
}
这个代码定义了一个名为circuit_breaker
的状态机:
- 初始状态为
Closed
- 定义了状态转移:例如在
HalfOpen
状态收到Successful
输入时转移到Closed
状态 - 定义了输出:例如在
Closed
状态收到Unsuccessful
输入时输出SetupTimer
使用示例:
// 初始化状态机,状态为Closed
let mut machine = circuit_breaker::StateMachine::new();
// 消费Successful输入,不进行状态转移
let _ = machine.consume(&circuit_breaker::Input::Successful);
// 消费Unsuccessful输入,状态转移到Open,输出SetupTimer
let output = machine.consume(&circuit_breaker::Input::Unsuccessful).unwrap();
// 检查输出
if let Some(circuit_breaker::Output::SetupTimer) = output {
// 设置定时器...
}
// 检查状态
if let circuit_breaker::State::Open = machine.state() {
// 做一些事情...
}
完整示例
以下是使用DSL定义状态机的完整示例代码:
use rust_fsm::*;
// 定义状态机
state_machine! {
#[derive(Debug)]
/// 门禁系统状态机
door_access(Closed)
Closed(KeyInserted) => Opened,
Opened => {
KeyRemoved => Closed,
Timeout => Closed [SoundAlarm]
}
}
fn main() {
// 创建状态机实例
let mut door = door_access::StateMachine::new();
// 初始状态应为Closed
assert!(matches!(door.state(), door_access::State::Closed));
// 插入钥匙开门
let output = door.consume(&door_access::Input::KeyInserted);
assert!(output.unwrap().is_none()); // 无输出
assert!(matches!(door.state(), door_access::State::Opened));
// 钥匙移除关门
let output = door.consume(&door_access::Input::KeyRemoved);
assert!(output.unwrap().is_none());
assert!(matches!(door.state(), door_access::State::Closed));
// 再次开门
door.consume(&door_access::Input::KeyInserted).unwrap();
// 超时关门并触发警报
let output = door.consume(&door_access::Input::Timeout).unwrap();
assert!(matches!(output, Some(door_access::Output::SoundAlarm)));
assert!(matches!(door.state(), door_access::State::Closed));
println!("Door access state machine test completed successfully!");
}
使用自定义类型
您可以使用自己的类型作为输入、输出或状态:
use rust_fsm::*;
pub enum Input {
KeyInserted,
KeyRemoved,
Timeout,
}
pub enum State {
Closed,
Opened,
}
pub enum Output {
SoundAlarm,
}
state_machine! {
#[state_machine(input(crate::Input), state(crate::State), output(crate::Output))]
door_access(Closed)
Closed(KeyInserted) => Opened,
Opened => {
KeyRemoved => Closed,
Timeout => Closed [SoundAlarm]
}
}
特性标志
默认特性
std
- 需要std
环境的特性dsl
- 从rust-fsm
重新导出rust-fsm-dsl
,推荐开启
非默认特性
diagram
- 在文档注释中生成Mermaid状态图
在no_std
环境中使用
默认启用了std
特性。要在no_std
环境中使用,可以这样导入:
rust-fsm = { version = "0.8", default-features = false, features = ["dsl"] }
安装
在项目目录中运行以下Cargo命令:
cargo add rust-fsm-dsl
或者在Cargo.toml中添加:
rust-fsm-dsl = "0.8.0"
完整Demo示例
下面是一个完整的交通灯状态机示例:
use rust_fsm::*;
// 定义交通灯状态机
state_machine! {
#[derive(Debug)]
/// 交通灯状态机
traffic_light(Red)
Red(Timer) => Green,
Green(Timer) => Yellow,
Yellow(Timer) => Red
}
fn main() {
// 创建状态机实例
let mut light = traffic_light::StateMachine::new();
// 初始状态应为Red
assert!(matches!(light.state(), traffic_light::State::Red));
// 打印当前状态
println!("Current state: {:?}", light.state());
// 红灯变绿灯
light.consume(&traffic_light::Input::Timer).unwrap();
println!("State changed to: {:?}", light.state());
assert!(matches!(light.state(), traffic_light::State::Green));
// 绿灯变黄灯
light.consume(&traffic_light::Input::Timer).unwrap();
println!("State changed to: {:?}", light.state());
assert!(matches!(light.state(), traffic_light::State::Yellow));
// 黄灯变红灯
light.consume(&traffic_light::Input::Timer).unwrap();
println!("State changed to: {:?}", light.state());
assert!(matches!(light.state(), traffic_light::State::Red));
println!("Traffic light state machine test completed successfully!");
}
这个示例展示了:
- 定义了一个简单的交通灯状态机,包含Red、Green和Yellow三个状态
- 每个状态在收到Timer输入时都会转移到下一个状态
- 通过断言验证状态转移的正确性
- 打印状态变化过程
1 回复
Rust有限状态机(FSM)库rust-fsm-dsl使用指南
简介
rust-fsm-dsl是一个基于领域特定语言(DSL)的Rust有限状态机(FSM)库,它提供了一种声明式的方式来定义和实现状态机。这个库特别适合需要清晰状态转换逻辑的应用场景,如协议实现、游戏AI、业务工作流等。
主要特性
- 简洁的DSL语法定义状态机
- 编译时状态转换验证
- 零成本抽象
- 支持状态进入/退出动作
- 类型安全的状态管理
基本使用方法
1. 添加依赖
首先在Cargo.toml中添加依赖:
[dependencies]
rust-fsm-dsl = "0.3"
2. 定义状态机
使用DSL宏定义状态机:
use rust_fsm_dsl::*;
state_machine! {
derive(Debug)
CircuitBreaker(Closed)
Closed => Open [Failure]
Open => HalfOpen [Timer]
HalfOpen => Open [Failure]
HalfOpen => Closed [Success]
}
3. 使用状态机
fn main() {
// 创建状态机实例,初始状态为Closed
let mut machine: StateMachine<CircuitBreaker> = StateMachine::new();
// 处理事件
let result = machine.consume(&CircuitBreakerInput::Failure);
assert!(matches!(result, Ok(Some(CircuitBreakerOutput::Open))));
// 检查当前状态
assert!(matches!(machine.state(), CircuitBreakerState::Open));
}
完整示例代码
use rust_fsm_dsl::*;
// 定义订单状态机
state_machine! {
derive(Debug, Clone)
OrderStateMachine(New)
New => PendingPayment [Submit]
PendingPayment => Paid [Pay(amount: f64)]
PendingPayment => Cancelled [Cancel(reason: String)]
Paid => Shipped [Ship(tracking_number: String)]
Shipped => Delivered [Deliver]
Shipped => Returned [Return(reason: String)]
impl OrderStateMachine {
fn on_pay(&mut self, amount: f64) -> Result<(), String> {
if amount <= 0.0 {
Err("Payment amount must be positive".to_string())
} else {
self.amount_paid = amount;
Ok(())
}
}
fn on_ship(&mut self, tracking_number: String) {
self.tracking_number = Some(tracking_number);
}
}
}
fn main() {
// 创建订单状态机实例
let mut order_machine: StateMachine<OrderStateMachine> = StateMachine::new();
// 提交订单
let result = order_machine.consume(&OrderStateMachineInput::Submit);
assert!(matches!(result, Ok(Some(OrderStateMachineOutput::PendingPayment))));
// 支付订单
let pay_result = order_machine.consume(&OrderStateMachineInput::Pay(100.0));
assert!(matches!(pay_result, Ok(Some(OrderStateMachineOutput::Paid))));
// 发货
let ship_result = order_machine.consume(&OrderStateMachineInput::Ship("123456789".to_string()));
assert!(matches!(ship_result, Ok(Some(OrderStateMachineOutput::Shipped))));
// 检查当前状态
assert!(matches!(order_machine.state(), OrderStateMachineState::Shipped));
println!("Order state: {:?}", order_machine.state());
}
最佳实践
- 为每个状态机定义单独的模块
- 使用
#[derive(Debug)]
以便调试 - 对关键业务逻辑添加单元测试
- 考虑使用
typenum
crate处理复杂状态条件 - 对于性能敏感场景,考虑使用
no_std
模式
性能考虑
rust-fsm-dsl在编译时生成状态转换表,运行时开销极小。状态转换是O(1)操作,只涉及简单的表查找和函数调用。
总结
rust-fsm-dsl提供了一种类型安全且高效的方式来在Rust中实现有限状态机。它的DSL语法使得状态机定义清晰易懂,同时保持了Rust的性能优势。通过编译时验证,可以避免许多常见的状态机实现错误。