Trait Objects with associated types part 3

#1

What I am trying to do is to have 2 traits a Task which would contain information and a Runner which would be able to handle a task to run it. I would then have a Runner registry that would be indexed by task type to do the dispatch of the right runner for a particular task.

I have a working solution that makes use of purely trait objects with no associated types but this gives more leeway to incorrectly implement a Runner since in my case it would have more than 1 method.

I saw a previous discussion at Trait Objects with Associated Types part 2 on something similar and got the idea of using this method of erasing the associated type.

I have a few questions,

  • Am I on the right path in this solution? Would this be a reasonable API
  • I am a little stumped on the 'static lifetime I had to put on the ErasedRunner impl. I am wondering what are the implications of requiring the task to have a static lifetime would be
  • Is there a way to have a default implementation for fn as_any(&self) -> &dyn Any for any Task without requiring implementers of Task to write the same code? Having it as a default trait method requires Task to be Sized but then Task cannot be a trait object.
use std::any::Any;
use std::collections::HashMap;

trait Task {
    fn task_type(&self) -> &'static str;
    fn as_any(&self) -> &dyn Any;
}

trait Runner {
    type Assoc: Task;

    fn run(&self, task: &Self::Assoc);
}

#[derive(Debug)]
struct FooTask;
impl Task for FooTask {
    fn task_type(&self) -> &'static str {
        "foo"
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
}

struct FooRunner;
impl Runner for FooRunner {
    type Assoc = FooTask;

    fn run(&self, task: &Self::Assoc) {
        dbg!(task);
    }
}

#[derive(Debug)]
struct BarTask;
impl Task for BarTask {
    fn task_type(&self) -> &'static str {
        "bar"
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
}

struct BarRunner;
impl Runner for BarRunner {
    type Assoc = BarTask;

    fn run(&self, task: &Self::Assoc) {
        dbg!(task);
    }
}

trait ErasedRunner {
    fn run(&self, task: Box<dyn Task>);
}

impl<M, A: Task + 'static> ErasedRunner for M
where
    M: Runner<Assoc = A>,
{
    fn run(&self, task: Box<dyn Task>) {
        let t: &A = task.as_any().downcast_ref().unwrap();
        self.run(t)
    }
}

fn main() {
    let mut runners: HashMap<&'static str, Box<ErasedRunner>> = HashMap::new();//vec![Box::new(FooRunner), Box::new(BarRunner)];
    runners.insert("foo", Box::new(FooRunner));
    runners.insert("bar", Box::new(BarRunner));
    
    runners.get("foo").unwrap().run(Box::new(FooTask));
}

(Playground)

0 Likes

#2

You could require that any Task must also impl Any.

0 Likes