1

A trait like this prevents &dyn DoAction because of the generic function:

trait DoAction {
    fn action<T: T1 + T2>(&self, s: &T) { 
        s.action_t1();
        s.action_t2();
    }
}

Is there a way to write a function where the Vec contains different concrete types, but they all implement the trait DoAction?

fn run_all(runners: Vec<&impl DoAction>) {}

The main issue I want to solve is being able to loop over these different concrete types - but I cannot use Vec<&dyn T> trait objects as decribed in How do I create a heterogeneous collection of objects? because of the generic trait function.

For example:

struct SA {
    sa: u32,
}

struct SB {
    sb: u32,
}

trait T1 {
    fn action_t1(&self) -> bool {
        true
    }
}

trait T2 {
    fn action_t2(&self) -> bool {
        true
    }
}

impl T1 for SA {}
impl T1 for SB {}
impl T2 for SA {}
impl T2 for SB {}

impl T1 for &SA {}
impl T1 for &SB {}
impl T2 for &SA {}
impl T2 for &SB {}

trait DoAction {
    fn action<T: T1 + T2>(&self, s: &T) {
        s.action_t1();
        s.action_t2();
    }
}

struct Runner1 {}
impl DoAction for Runner1 {}

struct Runner2 {}
impl DoAction for Runner2 {}

fn run_all(runners: Vec<&impl DoAction>, s: (impl T1 + T2)) {
    for r in runners {
        r.action(&s);
    }
}

fn main() {
    let a = SA { sa: 123 };

    let r1 = Runner1 {};
    let r2 = Runner2 {};

    let ls = vec![&r1, &r2];
    run_all(ls, &a);
}

Playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
zino
  • 1,222
  • 2
  • 17
  • 47
  • Does this answer your question? [Why does a generic method inside a trait require trait object to be sized?](https://stackoverflow.com/questions/42620022/why-does-a-generic-method-inside-a-trait-require-trait-object-to-be-sized) – Ibraheem Ahmed Nov 10 '20 at 16:47
  • Partially yes, I understand the generic requires a concrete type. But Im not sure of the possible options for looping over different concrete types having the same trait - so it's impossible to do? – zino Nov 10 '20 at 17:15

2 Answers2

4

Since such a function would only work for some concrete type, you can work around this by making a trait that "concretizes" the method and implement it for all types implementing DoAction:

trait DoConcreteAction<T> {
    fn concrete_action(&self, s: &T);
}

impl<T, U> DoConcreteAction<U> for T
where
    T: DoAction,
    U: T1 + T2,
{
    fn concrete_action(&self, s: &U) {
        self.action(s)
    }
}

fn run_all<T>(runners: Vec<&dyn DoConcreteAction<T>>, s: &T) {
    for r in runners {
        r.concrete_action(&s);
    }
}

fn main() {
    let a = SA { sa: 123 };

    let r1 = Runner1 {};
    let r2 = Runner2 {};

    let ls: Vec<&dyn DoConcreteAction<SA>> = vec![&r1, &r2];
    run_all(ls, &a);
}

Permalink to the playground

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
mcarton
  • 27,633
  • 5
  • 85
  • 95
  • Thanks. Is it possible to keep the `impl T1 + T2` as the second arg to `run_all`? So I would roughly have a `(Vec, Vec)`, and then pass every `T1+T2` to every `DoAction.action(x)`. I think the solution works because SA is passed to the generic `DoConcreteAction` making it concrete, but this also removes the `impl T1+T2` trait bound on `run_all`. Im thinking maybe I should just use an enum here instead of generics. – zino Nov 10 '20 at 19:25
0

You can create a super trait that is automatically implemented for any type that implements T1 and T2:

trait T1AndT2: T1 + T2 {}

impl<T: T1 + T2> T1AndT2 for T {}

And then change DoAction to accept a trait object (T1AndT2) instead of generics:

trait DoAction {
    fn action(&self, s: &dyn T1AndT2) { 
        s.action_t1();
        s.action_t2();
    }
}

Now that DoAction does not use generics, you can create a vector of DoAction objects:

let ls: Vec::<&dyn DoAction> = vec![&r1, &r2];

And use it in your function:

run_all(ls);

Runnable Playground Link

Ibraheem Ahmed
  • 11,652
  • 2
  • 48
  • 54
  • This works with the example code, but with my code base the `DoAction` `action` function will have different implementations for each struct (not just the default), and each implementation will take a subset of the traits from `DoAction` `action` (for example, the generic function on the struct may be `fn action` (notice it does not include T2). In my code base I have T1 to T5 – zino Nov 10 '20 at 17:49