Search code examples
rustassociated-typesdynamic-dispatchtrait-objects

Can I have a collection over trait objects where their associated types implement a shared trait?


I know that generally you can't have something like Vec<dyn Iterator>> because of course you need to specify the associated type.

Now, I can have Vec<Box<dyn Iterator<Item=i32>>> of course.

But what about something like this:

trait Foo {}

#[derive(Clone)]
struct Bla;
impl Foo for Bla {}

#[derive(Clone)]
struct Bar;
impl Foo for Bar {}

fn main () {
    // this works of course
    let x: Vec<Box<dyn Iterator<Item=i32>>> = vec![];
    
    // this can be defined
    let mut y: Vec<Box<dyn Iterator<Item=Box<dyn Foo>>>> = vec![];
    
    // but how do I use it? these don't work
    let first_it = Box::new(std::iter::repeat(Box::new(Bar{})));
    let second_it = Box::new(std::iter::repeat(Box::new(Bla{})).take(10));
    
    y.push(first_it);
    y.push(second_it);
}

The compiler complains:

expected `std::iter::Repeat<Box<Bar>>` to be an iterator that yields `Box<dyn Foo>`, but it yields `Box<Bar>`

So. Obviously then Box<Bar>, despite Bar implementing Foo, is not considered an acceptable subtype for Box<dyn Foo>.

I know Rust doesn't have the same super-high-order associated types that some other languages have. Is what I'm trying to do fundamentally impossible or just not supported in Rust?

In my mind, there shouldn't be an inherent type issue with using y. All the elements it contains will be iterators that yield certain items, all of which will implement the same trait, Foo.

Or am I just doing something wrong in creating the trait object?


Solution

  • There is a structural incompatibility between Iterator<Item=Box<Bar>> and Iterator<Item=Box<dyn Foo>>. If you aren't aware, Box<Bar> has a different size than Box<dyn Foo> (single pointer vs two pointers internally).

    You can convert, or rather coerce, a Box<Bar> into a Box<dyn Foo> because Bar implements Foo. But you cannot pass off a Iterator<Item=Box<Bar>> as a Iterator<Item=Box<dyn Foo>> because there needs to be a place for that conversion to happen.

    So yes, you can have such a collection, but you need to use the right trait objects proactively:

    let mut y: Vec<Box<dyn Iterator<Item=Box<dyn Foo>>>> = vec![];
    
    let first_it = Box::new(std::iter::repeat(Box::new(Bar{})).map(|b| b as Box<dyn Foo>));
    let second_it = Box::new(std::iter::repeat(Box::new(Bla{})).map(|b| b as Box<dyn Foo>).take(10));
    
    y.push(first_it);
    y.push(second_it);