I'm thinking about a state machine that forces me to implement all state transitions at compile time, so no dynamic allocation is used. I'd like to do
let machine = RtspMachine::begin();
machine.event(...);
machine.event(...);
and event
would change the internal state of the machine.
Here's my sketch:
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
}
The problem is that in order to ensure in compile time that I implemented all transitions, the RtspState
must be generic, so I can match over its types. But then, the RtspMachine
would have to be generic, thus I'd not be able to simply do machine.event
to modify its internal state because its type would change on a state transition.
I thought of doing
enum RtspState {
Init,
Ready,
Playing,
Recording,
}
but then I cannot match over the state, because RtspState::Init
is not a type, but a variant.
One solution would be to make a enum RtspMachineWrapper
:
enum RtspMachineWrapper {
RtspMachine<Init>,
RtspMachine<Ready>,
RtspMachine<Playing>,
RtspMachine<Recording>
}
but then I'd have to reimplement every RtspMachine
call to RtspMachineWrapper
by doing a large match over all states.
What should I do?