实现没有动态分配和编译时断言的简单状态机

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.
}