Actor<dyn Trait>

If you need to have a group of different actors that all implement the same interface and that can be used interchangeably behind that standard interface, there are several options available. However Actor<dyn Trait> is not one of them, for reasons that will be explained below!

Use a trait on the actor side: Actor<Box<dyn Trait>>

There is a macro actor_of_trait! to support this. This all looks clean and minimal in the source. On the caller side, all they see is a standard-looking actor interface. However compared to a non-trait actor, this adds an extra indirection to all calls due to the Box. Here's an example:

use stakker::*;
use std::time::Instant;

// Trait definition
type Animal = Box<dyn AnimalTrait>;
trait AnimalTrait {
    fn sound(&mut self, cx: CX![Animal]);
}

struct Cat;
impl Cat {
    fn init(_: CX![Animal]) -> Option<Animal> {
        Some(Box::new(Cat))
    }
}
impl AnimalTrait for Cat {
    fn sound(&self, _: CX![Animal]) {
        println!("Miaow");
    }
}

struct Dog;
impl Dog {
    fn init(_: CX![Animal]) -> Option<Animal> {
        Some(Box::new(Dog))
    }
}
impl AnimalTrait for Dog {
    fn sound(&mut self, _: CX![Animal]) {
        println!("Woof");
    }
}

pub fn main() {
    let mut stakker = Stakker::new(Instant::now());
    let s = &mut stakker;

    let animal1 = actor_of_trait!(s, Animal, Cat::init(), ret_nop!());
    let animal2 = actor_of_trait!(s, Animal, Dog::init(), ret_nop!());

    let mut list: Vec<Actor<Animal>> = Vec::new();
    list.push(animal1.clone());
    list.push(animal2.clone());

    for a in list {
        call!([a], sound());
    }
    s.run(Instant::now(), false);
}

Use a trait on the caller side: Box<dyn Trait>

This involves wrapping the actors in a trait that forwards calls, and then boxing it to make it dynamic. So this also adds an indirection, but on the caller side. This is more verbose than doing it on the actor side, and the calls don't look like other actor calls. Here's an example:

use stakker::*;
use std::time::Instant;

// External interface of all Animals
trait Animal {
    fn sound(&self);
}

// A particular animal, wraps any actor that implements AnimalActor
struct AnAnimal<T: AnimalActor + 'static>(ActorOwn<T>);
impl<T: AnimalActor + 'static> Animal for AnAnimal<T> {
    fn sound(&self) {
        call!([self.0], sound());
    }
}

// Internal interface of animal actors
trait AnimalActor: Sized {
    fn sound(&self, cx: CX![]);
}

struct Cat;
impl Cat {
    fn init(_: CX![]) -> Option<Self> {
        Some(Self)
    }
}
impl AnimalActor for Cat {
    fn sound(&self, _: CX![]) {
        println!("Miaow");
    }
}

struct Dog;
impl Dog {
    fn init(_: CX![]) -> Option<Self> {
        Some(Self)
    }
}
impl AnimalActor for Dog {
    fn sound(&self, _: CX![]) {
        println!("Woof");
    }
}

fn main() {
    let mut stakker = Stakker::new(Instant::now());
    let s = &mut stakker;

    let animal1 = AnAnimal(actor!(s, Dog::init(), ret_nop!()));
    let animal2 = AnAnimal(actor!(s, Cat::init(), ret_nop!()));

    let mut list: Vec<Box<dyn Animal>> = Vec::new();
    list.push(Box::new(animal1)); // <- dyn coercion occurs here
    list.push(Box::new(animal2)); // <- dyn coercion occurs here

    for a in list {
        a.sound();
    }
    s.run(Instant::now(), false);
}

Use Fwd and ActorOwnAnon

Instead of using a trait, it's also possible to use Fwd to capture the entry point of an arbitrary actor, and to pass that to other actors that only care about the forwarding interface. The extra indirection is also present in this solution, since the call must pass via the Fwd handler. However this is a lot more flexible than traits.

Where you want another actor to not only have a Fwd instance but also to hold the owning reference to the actor, then you can use ActorOwnAnon. That way if that actor dies, the referenced actor dies too. This allows owning the actor without being exposed to the type. So you can keep a Vec<ActorOwnAnon> pointing to different kinds of actors for example.

Why Actor<dyn Trait> can't be supported

Rc<dyn Trait> can be done, so why isn't Actor<dyn Trait> possible?

To enable dyn Trait requires the actor runtime to be changed to use A: ?Sized, where A is the actor's Self type. Unfortunately Rust does not support ?Sized values inside an enum, apparently due to it inhibiting layout optimisations, and Stakker requires an enum to enable switching between the three actor states (Prep, Ready and Zombie). Maybe Rust could have a #[repr(unsizable)] for enums to support this one day, but it doesn't right now.

In addition CoerceUnsized is still unstable at the time of writing. This is the approved way to do the "dyn coercion" which converts an Rc<impl Trait> to an Rc<dyn Trait>. However that can be worked around, I believe. So that isn't the blocker.

Looking at alternative approaches, it seemed like implementing a custom enum in unsafe code might be possible using union, but that is also a dead end due to union only supporting Copy types on stable at present. I have an unsized_enum crate which I believe is sound and could be the basis for Actor<dyn Trait> in Stakker, but I don't want to force it on all Stakker users. I'd like to be able to offer a safe alternative as well. (Update: As of Feb-2021 'union' supports ManuallyDrop which allows ?Sized, so that might offer a better way, although it still requires unsafe.)

So unfortunately it's not possible to do Actor<dyn Trait> right now, and one of the alternatives must be used instead.