zino
zino

Reputation: 1472

Is it possible to store different concrete types implementing the same non-object safe trait in a collection?

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

Upvotes: 1

Views: 98

Answers (2)

mcarton
mcarton

Reputation: 30061

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

Upvotes: 4

Ibraheem Ahmed
Ibraheem Ahmed

Reputation: 13538

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

Upvotes: 0

Related Questions