Simple state machine with generic event type

I came up with this simple state machine in Rust, where I meet 2 criteria. The most important: being able to match over all states with errors if I don't match.

The other reason is that, if I do all the events PlayResponse, DescribeResponse, etc inside a single enum, I cannot have the trait implementations OnEvent separated for each event. This is much important for code readability. Matching over all states and all events inside a single RtspMachine::on_event call would give a very large code block.

struct Response {}

struct PlayResponse(Response);
struct DescribeResponse(Response);

impl From<Response> for PlayResponse {
    fn from(response: Response) -> Self {
        PlayResponse(response)
    }
}

impl From<Response> for DescribeResponse {
    fn from(response: Response) -> Self {
        DescribeResponse(response)
    }
}

enum RtspState {
    Init,
    Ready,
    Playing,
    Recording,
}

struct RtspMachine {
    state: RtspState
}

pub trait OnEvent<T> {
    fn on_event(&mut self, event: &T) -> std::result::Result<(), ()>;
}

impl OnEvent<PlayResponse> for RtspMachine {
    fn on_event(&mut self, event: &PlayResponse) -> std::result::Result<(), ()> {
        self.state = RtspState::Playing;
        Ok(())
    }
}

impl OnEvent<DescribeResponse> for RtspMachine {
    fn on_event(&mut self, event: &DescribeResponse) -> std::result::Result<(), ()> {
        self.state = RtspState::Init;
        Ok(())
    }
}

This code worked very well, until I reached a problem with generics of a function. If I add this:

fn do_something<T: OnEvent<T>>() {
    let rtsp_machine = RtspMachine{state: RtspState::Init};
    rtsp_machine.on_event(&T::from(Response{}));
}

I get:

error[E0308]: mismatched types
  --> src/lib.rs:49:36
   |
47 | fn do_something<T: OnEvent<T>>() {
   |                 - this type parameter
48 |     let rtsp_machine = RtspMachine{state: RtspState::Init};
49 |     rtsp_machine.on_event(&T::from(Response{}));
   |                                    ^^^^^^^^^^ expected type parameter `T`, found struct `Response`
   |
   = note: expected type parameter `T`
                      found struct `Response`

error[E0277]: the trait bound `RtspMachine: OnEvent<T>` is not satisfied
  --> src/lib.rs:49:18
   |
49 |     rtsp_machine.on_event(&T::from(Response{}));
   |                  ^^^^^^^^ the trait `OnEvent<T>` is not implemented for `RtspMachine

Rust Playground: Rust Playground

I know why. Instead of C++, in Rust, the generic T has to specify that it is one of the event types: PlayResponse, DescribeResponse, etc.

Is there a way to make this work?

You need to bound RtspMachine here, whic isn’t one of the generic parameters. For that, you use a where clause:

fn do_something<T>() where
    RtspMachine: OnEvent<T>,
    T: From<Response>
{
    let mut rtsp_machine = RtspMachine{state: RtspState::Init};
    rtsp_machine.on_event(&T::from(Response{}));
}

why this works?

The trait OnEvent<T> is not implemented for RtspMachine. Only some specific Ts have valid implementations for OnEvent<T>.

It's implemented for RtspMachine right here:

impl OnEvent<PlayResponse> for RtspMachine { ... }
impl OnEvent<DescribeResponse> for RtspMachine { ... }

Because RtspMachine appears in the for clause, it goes on the left side of the bound. The bound says that when the compiler sees do_something::<PlayResponse>(), it should verify that the implementation of OnEvent<PlayResponse> exists. If you tried to call do_something::<usize>(), you'd get a compile error because the implementation doesn't exist.

That doesn't require RtspMachine to implement OnEvent<T> for any T, it only requires the caller of do_something to choose a T such that RtspMachine implements OnEvent<T> with that specific T

1 Like

What about this case:

fn do_something<T: OnEvent<T>>() where RtspMachine: OnEvent<T>, T: From<Response>{
    let mut rtsp_machine = RtspMachine{state: RtspState::Init};
    rtsp_machine.on_event(&T::from(Response{}));
    rtsp_machine.on_event(&PlayResponse::from(Response{}));
}

Here, with generic T, it works. But I don't see why I cannot do for a specific T that implements the From.

Error:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:53:27
   |
50 | fn do_something<T: OnEvent<T>>() where RtspMachine: OnEvent<T>, T: From<Response>{
   |                 - this type parameter
...
53 |     rtsp_machine.on_event(&PlayResponse::from(Response{}));
   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `T`, found struct `PlayResponse`
   |
   = note: expected reference `&T`
              found reference `&PlayResponse`

Playground link: Rust Playground

That feels like it should work, especially since the non-generic version does:

fn do_something2() {
    let mut rtsp_machine = RtspMachine{state: RtspState::Init};
    rtsp_machine.on_event(&PlayResponse::from(Response{}));
}

I don't see why the generic bounds should affect the behavior of the non-generic code. Hopefully someone with more experience can explain it.


This works, but seems overly verbose:

fn do_something<T>() where
    RtspMachine: OnEvent<T> + OnEvent<PlayResponse>,
    T: From<Response>
{
    let mut rtsp_machine = RtspMachine{state: RtspState::Init};
    rtsp_machine.on_event(&T::from(Response{}));
    rtsp_machine.on_event(&PlayResponse::from(Response{}));
}

Issue 24066. And another that hasn't been marked as a duplicate yet (CC @Cerberuser).

The below also works, unsurprisingly I guess.

fn do_something<T: OnEvent<T>>() where RtspMachine: OnEvent<T>, T: From<Response>{
    let mut rtsp_machine = RtspMachine{state: RtspState::Init};
    rtsp_machine.on_event(&T::from(Response{}));
    OnEvent::<PlayResponse>::on_event(&mut rtsp_machine, &PlayResponse::from(Response{}));
}
3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.