Implementing State Machines

This guide is for authors of the protocols, not for users of the protocols. Read it if you want to write an new protocol implementation on top of raw rotor::Machine. Otherwise consult on your protocol documenation (probably good links are in Ecosystem)

Boilerplate

This is just a blanket stub implementation I usually start with, filling in methods one by one:

extern crate rotor;

use rotor::{Machine, EventSet, Scope, Response};
use rotor::void::{unreachable, Void};

impl<C> Machine for Fsm<C> {
    type Context = C;
    type Seed = Void;
    fn create(seed: Self::Seed, _scope: &mut Scope<C>)
        -> Response<Self, Void>
    {
        unreachable(seed)
    }
    fn ready(self, _events: EventSet, _scope: &mut Scope<C>)
        -> Response<Self, Self::Seed>
    {
        unimplemented!();
    }
    fn spawned(self, _scope: &mut Scope<C>) -> Response<Self, Self::Seed>
    {
        unimplemented!();
    }
    fn timeout(self, _scope: &mut Scope<C>) -> Response<Self, Self::Seed>
    {
        unimplemented!();
    }
    fn wakeup(self, _scope: &mut Scope<C>) -> Response<Self, Self::Seed>
    {
        unimplemented!();
    }
}

There are two intricate things here:

  1. We use void crate and void::Void type to denote that seed can’t be created so create method is never called

    Keep the type void unless your machine spawns new state machines. And in the latter case it’s advised to use some abstraction for state machine spawning. There is an rotor_stream::Accept for accepting sockets, more to come.

  2. Implementation should almost always use generic context (impl<C>) as only end application should know the exact layout of a context.

    You may limit the generic with some traits (impl<C: HttpContext>).

    Often, your state machine doesn’t rely on context at all. Currently, this requires adding a PhantomData<*const C> marker to state machine. The marker_ is zero-sized, so it just a little bit of boring code.