实现没有动态分配和编译时断言的简单状态机
Implementing a simple state machine with no dynamic allocation and compile time assertion
我正在考虑强制我在编译时实现所有状态转换的状态机,因此不使用动态分配。我想做
let machine = RtspMachine::begin();
machine.event(...);
machine.event(...);
和event
会改变机器的内部状态。
这是我的草图:
struct Init {}
struct Ready{}
struct Playing{}
struct Recording{}
struct _RtspState<T> {
t: T
}
type RtspState<T> = _RtspState<T>;
trait StateChange where Self: Sized {
fn from<T>(self, state: &RtspState<T>, event: &Event) -> std::result::Result<RtspMachine, EventError>;
}
impl StateChange for RtspMachine {
fn from(self, state: RtspState<Init>, event: &Event) -> std::result::Result<RtspMachine, EventError> {
//...
}
fn from(self, state: RtspState<Init>, event: &Event) -> std::result::Result<RtspMachine, EventError> {
//...
}
//...
}
pub(crate) struct RtspMachine {
state: RtspState
}
问题是为了确保在编译时我实现了所有转换,RtspState
必须是通用的,这样我才能匹配它的类型。但是,RtspMachine
必须是通用的,因此我不能简单地做 machine.event
来修改它的内部状态,因为它的类型会在状态转换时改变。
我想到了
enum RtspState {
Init,
Ready,
Playing,
Recording,
}
但是我无法匹配状态,因为 RtspState::Init
不是类型,而是变体。
一个解决方案是制作 enum RtspMachineWrapper
:
enum RtspMachineWrapper {
RtspMachine<Init>,
RtspMachine<Ready>,
RtspMachine<Playing>,
RtspMachine<Recording>
}
但是我必须通过对所有状态进行大型匹配来重新实现对 RtspMachineWrapper
的每个 RtspMachine
调用。
我该怎么办?
在堆栈上执行此操作的唯一方法是使用 enum
.
您可以在匹配时跳过 enum
的无用变体:
match my_enum_value {
MyEnum::Value1(state) => { /* do something */ },
MyEnum::Value2(state) => { /* do something else */ },
_ => { /* do nothing? */ },
}
在我看来,您混淆了状态机经常使用的两种略有不同的模式。
首先是通过将 FSM 封装在枚举中来确保每个转换都得到很好的处理。
enum RtspState {
Init,
Ready,
Playing,
Recording,
}
impl RtspState {
pub fn on_event(&self, event: &Event) -> std::result::Result<Self, EventError> {
match self {
Init => Ok(Ready),
Ready => if event.foo { Ok(Playing) } else { Err(EventError::new("bang")) },
...
// The compiler will complain if we miss any.
}
}
}
pub fn main() -> EventError {
let state = RtspState::Init;
let state = state.on_event(an_event());
let state = state.on_event(foo_event());
...
}
另一种模式是创建一个 FSM,其中在编译时不可能发生无效事件。这不使用枚举,而是使用您在示例中使用的单独结构。区别在于每个结构类型只支持有限的事件。
struct Init {}
struct Ready {}
struct Playing {}
struct Recording {}
struct AnEvent {}
struct FooEvent {}
impl Init {
pub fn on_an_event(&self, e: &AnEvent) -> Ready {
Ready
}
}
impl Ready {
pub fn on_foo_event(&self, e: &FooEvent) -> Playing {
Playing
}
}
pub fn main() {
let state = Init;
let state = state.on_an_event(an_event());
let state = state.on_foo_event(foo_event());
// The compiler will complain if we try to do an invalid event.
}
我正在考虑强制我在编译时实现所有状态转换的状态机,因此不使用动态分配。我想做
let machine = RtspMachine::begin();
machine.event(...);
machine.event(...);
和event
会改变机器的内部状态。
这是我的草图:
struct Init {}
struct Ready{}
struct Playing{}
struct Recording{}
struct _RtspState<T> {
t: T
}
type RtspState<T> = _RtspState<T>;
trait StateChange where Self: Sized {
fn from<T>(self, state: &RtspState<T>, event: &Event) -> std::result::Result<RtspMachine, EventError>;
}
impl StateChange for RtspMachine {
fn from(self, state: RtspState<Init>, event: &Event) -> std::result::Result<RtspMachine, EventError> {
//...
}
fn from(self, state: RtspState<Init>, event: &Event) -> std::result::Result<RtspMachine, EventError> {
//...
}
//...
}
pub(crate) struct RtspMachine {
state: RtspState
}
问题是为了确保在编译时我实现了所有转换,RtspState
必须是通用的,这样我才能匹配它的类型。但是,RtspMachine
必须是通用的,因此我不能简单地做 machine.event
来修改它的内部状态,因为它的类型会在状态转换时改变。
我想到了
enum RtspState {
Init,
Ready,
Playing,
Recording,
}
但是我无法匹配状态,因为 RtspState::Init
不是类型,而是变体。
一个解决方案是制作 enum RtspMachineWrapper
:
enum RtspMachineWrapper {
RtspMachine<Init>,
RtspMachine<Ready>,
RtspMachine<Playing>,
RtspMachine<Recording>
}
但是我必须通过对所有状态进行大型匹配来重新实现对 RtspMachineWrapper
的每个 RtspMachine
调用。
我该怎么办?
在堆栈上执行此操作的唯一方法是使用 enum
.
您可以在匹配时跳过 enum
的无用变体:
match my_enum_value {
MyEnum::Value1(state) => { /* do something */ },
MyEnum::Value2(state) => { /* do something else */ },
_ => { /* do nothing? */ },
}
在我看来,您混淆了状态机经常使用的两种略有不同的模式。
首先是通过将 FSM 封装在枚举中来确保每个转换都得到很好的处理。
enum RtspState {
Init,
Ready,
Playing,
Recording,
}
impl RtspState {
pub fn on_event(&self, event: &Event) -> std::result::Result<Self, EventError> {
match self {
Init => Ok(Ready),
Ready => if event.foo { Ok(Playing) } else { Err(EventError::new("bang")) },
...
// The compiler will complain if we miss any.
}
}
}
pub fn main() -> EventError {
let state = RtspState::Init;
let state = state.on_event(an_event());
let state = state.on_event(foo_event());
...
}
另一种模式是创建一个 FSM,其中在编译时不可能发生无效事件。这不使用枚举,而是使用您在示例中使用的单独结构。区别在于每个结构类型只支持有限的事件。
struct Init {}
struct Ready {}
struct Playing {}
struct Recording {}
struct AnEvent {}
struct FooEvent {}
impl Init {
pub fn on_an_event(&self, e: &AnEvent) -> Ready {
Ready
}
}
impl Ready {
pub fn on_foo_event(&self, e: &FooEvent) -> Playing {
Playing
}
}
pub fn main() {
let state = Init;
let state = state.on_an_event(an_event());
let state = state.on_foo_event(foo_event());
// The compiler will complain if we try to do an invalid event.
}