insanitybit / aktors

Rust actor library with a bit of inspiration from Akka/Pykka
MIT License
46 stars 3 forks source link

Static-ify the actor/supervisor interface #1

Closed jrobsonchase closed 7 years ago

jrobsonchase commented 7 years ago

Makes the api a lot cleaner on the calling end at the cost of some gnarly type signatures. Still not 100% sure this is the best way to go about it, but at least it gets rid of most of the boxing.

Also added a gitignore for .vscode/.

insanitybit commented 7 years ago

Woah, very nice. I'll review this in the next few days (I lost my glasses... I don't feel like doing a CR without them :P) but it looks great. Thanks!

insanitybit commented 7 years ago

@Pursuit92 Would this work if the supervisor needed to supervise two different types of actor? Seems like it would need Any again.

insanitybit commented 7 years ago

In terms of reducing boxing and all of this Any I think I'll need to be able to build up the types at compile time. So if the supervisor supervises two actors of two types Actor and Actor the supervisor will need to be Supervisor<A | B> . Similarly, if Actor A can receive C or D, A must be C | D, and so Superivsor would really be <C | D | B>.

The problem being that I have no idea how to express this in rust. (the text is bolded idk why)

jrobsonchase commented 7 years ago

So in the case where you'd need to handle multiple types of messages and still wanted to keep the static dispatch, you'd just use an enum with variants for all of the different message types and then use matches inside your actors to extract the type of message that you want. The problem with that is that all of your actors would have to use the same enum as their message type, and I can see how that would be undesirable, especially when working with third-party actors.

Maybe if you were trying to use ActorA that takes message type A and ActorB that takes message type B with the same supervisor, you could have an enum

enum MyActor {
    A(ActorA),
    B(ActorB),
}

enum MyMessage {
    A(A),
    B(B),
}

impl Actor<MyMessage> for MyActor {
    fn on_message(&mut self, msg: MyMessage) {
        match self {
            A(a) => match msg {
                    A(a) => a.on_message(a),
                    _ => { /* do nothing or log error or whatever */ }
                }
           /* etc etc */
        }
    }
}

So that's crazy ugly now that I write it all out. But maybe it can all be macro-generated in a similar fashion to error_chain!?

Something like

impl_actor {
    // declare types to generate
    actor: MyActor,
    message: MyMessage,
    // Declare message enum variants
    message {
        A(A),
        B(B),
    }
    // Declare actor variants and which enum variant they should take
    actors {
        A(ActorA, A),
        B(ActorB, B),
    }
}

Or you're still free to use a Supervisor where A is Box<Actor<Box<Any>>> and M is Box<Any>.

insanitybit commented 7 years ago

Right but let's say I want an Actor that takes a message. In that message is a 'sender' - some other actor I can send messages to. So this message M needs to have an actor A in it. But A could be any actor. idk. All actors using the same enum won't work - what happens across crates?

The error-chain idea is interesting. I wonder how they build that thing up. That may provide a useful method, although fundamentally I think we're looking at a use case where higher kinded types are a requirements.

insanitybit commented 7 years ago

https://gist.github.com/insanitybit/f3eb7785444bed3a4c91eec18ce32c7d

I was writing a quick library to wrap dogstatsd-client so that it never blocks my main thread. Essentially I wrote a hacky actor that maps to OS threads (because I don't need to create a ton of these). There's a ton of boilerplate, but what you end up with is a typed actor. It would be nice if all of this could be generated.

I don't see any issue merging what you have right now in though - would help the API, and this is clearly an issue that will need more attention separately.