fitzgen / state_machine_future

Easily create type-safe `Future`s from state machines — without the boilerplate.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Non-trivial construction for statetypes

andreihu opened this issue · comments

I am trying to implement a non-trivial struct as a typestate. Non-trivial means here that it's not constructed by simply assigning members one-by-one.

enum Peer {
    #[state_machine_future(start, transitions(Connected))]
    Connecting {
        timer : Timeout,
        connector : TcpStreamNew,
    },

and construction code is:

impl Connecting {
    fn new(handle : &reactor::Handle, address : &SocketAddr) -> Self {
        let connect_fut = TcpStream::connect(&address, handle);
        let timeout_fut = Timeout::new(std::time::Duration::from_secs(5), handle).unwrap();
        Connecting {timer : timeout_fut, connector : connect_fut}
    }
}

This is not working for me as i can't make the statemachine's start function to accept handle and address arguments since it's expecting a TcpStreamNew and a Timeout, which are internal details of the struct, so i'd expect my clients to fill them up properly. My only workaround for this currently is to introduce a 'dummy' state, which is default constructible and immediately transition to the Connecting state (+ carrying arguments via the dummy state or the context), since the transition!() macro let's me to do the construction of the Connecting state via the new() member function.

Is there a better approach for this problem?

Thanks in advance

Hey, I also know any better way for doing it.

Did you tried to implement a new function for Peer that accepts the handle and the address, to start your state machine.

I have tried that, but it's not working for me.

So if i try:

    let fut_peer = Peer::start(&context.handle, context.address, context);

When new() looks like the following

impl Connecting {
    pub fn new(handle : &reactor::Handle, address : &SocketAddr) -> Self { ... }

I get a compiler error:

error[E0061]: this function takes 2 parameters but 3 parameters were supplied
   --> src/main.rs:167:20
    |
46  | #[derive(StateMachineFuture)]
    |          ------------------ defined here
...
167 |     let fut_peer = Peer::start(&context.handle, context.address, context);
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 2 parameters

I guess what's happening here is that the start() function is trying to directly initialize all members of the typestate struct and not going to invoke any associated functions to construct it. Since in Rust static new() functions are just some sort of convention (+ since there are no overloaded functions exist, some constructors are named differently), it'd be quite imperfect to distinguish new().

What is Connecting? A state of your Peer?
What I proposed was to add the new function to Peer.
It would also be nice to get more context (more code).

Yeah, Connecting is a state of the Peer enum. Now i got your idea, but seems shaky to me, e.g. how can i pass in the Context instance for the state machine? I guess start() is not just instantiating the Peer struct, but also sets up some other things under the hood.

Just make Peer::new() call Peer::start(). Just see it as a wrapper around Peer::start().