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?