Optional State Type Like Axum

I am working on a project sort of like axum, but for building state machines instead of a webserver. I have one problem. I am trying to handle state in the exact same way as Axum. I'd like to not force a user to register state, but appropriately handle whatever type they want to use as state.

pub struct AutoClientBuilder<S = ()> {
    handlers: Option<HashMap<String, StoredCallback<S>>>,
    tick_rate: Duration,
    initial_state: Option<String>,
    user_context: S,
}

impl AutoClientBuilder<()> {
    pub fn new() -> Self {
        Self {
            handlers: None,
            tick_rate: Duration::from_millis(50),
            initial_state: None,
            user_context: (),
        }
    }
    pub fn user_context<S2>(self, user_context: S2) -> AutoClientBuilder<S2> {
        AutoClientBuilder {
            handlers: self.handlers,
            tick_rate: self.tick_rate,
            initial_state: self.initial_state,
            user_context,
        }
    }
}

impl<S> AutoClientBuilder<S>
where
    S: Clone,
{
    // pub fn new() -> Self {
    //     Self {
    //         handlers: None,
    //         tick_rate: Duration::from_millis(50),
    //         initial_state: None,
    //         user_context: (),
    //     }
    // }
    pub fn add_state<I, C: Callback<S> + 'static>(
        self,
        name: String,
        f: impl IntoCallback<I, S, Callback = C>,
    ) -> Self {
        let mut handlers = self.handlers.unwrap_or_default();
        handlers.insert(name, Box::new(f.into_callback()));
        Self {
            handlers: Some(handlers),
            ..self
        }
    }
    pub fn tick_rate(mut self, tick_rate: Duration) -> Self {
        self.tick_rate = tick_rate;
        self
    }
    pub fn initial_state(mut self, initial_state: String) -> Self {
        self.initial_state = Some(initial_state);
        self
    }
    pub fn build(self) -> AutoClient<S> {
        let handlers = self.handlers.unwrap();
        let initial_state = self.initial_state.unwrap();
        AutoClient::new(handlers, self.tick_rate, initial_state, self.user_context)
    }
}

I need there to be some default type for state, because if not, I get an error due to the generic S not being clear at compile time. This current implementation also has an error, which is that the handlers are created with respect to S, so changing S obviously causes issues. Any help would be greatly appreciated! logical-state-machine/src/builder.rs at blah2 · roberte777/logical-state-machine · GitHub

You'll either need two different methods or one unified one.

Using different names (new and new_with_ctx) you can write multiple implementations. One method will obviously need the context as a parameter.

impl AutoClientBuilder<()> {
    pub fn new() -> Self {
        Self {
            handlers: None,
            tick_rate: Duration::from_millis(50),
            initial_state: None,
            user_context: (),
        }
    }
}
impl<S> AutoClientBuilder<S> {
    pub fn new_with_ctx(s: S) -> Self {
        Self {
            handlers: None,
            tick_rate: Duration::from_millis(50),
            initial_state: None,
            user_context: s,
        }
    }
}

The other option would be to require S: Default an then use that in the implementation:

impl<S> AutoClientBuilder<S> where S: Default {
    pub fn new(s: S) -> Self {
        Self {
            handlers: None,
            tick_rate: Duration::from_millis(50),
            initial_state: None,
            user_context: S::default(),
        }
    }
}

This will also work for (), because the unit tuple implements Default.


Addressing your other issue, you need to think about what should happen if handler contains some entries. What should happen in this case? One possiblity would be to panic if the map is not empty, but you could also require a mapping function. It's just depending on your domain and what operations make sense.

1 Like

Thanks for the response! This is similar to what I ended up going with in the meantime and does work. However, it’s not how Axum functions, which is what I’m trying to figure out.