Search code examples
rustpolymorphism

Vector of polymorphic structs with an associated type in trait


I am trying to understand how polymorphism works when using a trait with an associated type. Consider the following trait:

trait Animal {
    fn talk (&self);
}

This trait is used by the following structures:

struct Dog;
struct Cow;

impl Animal for Dog {
    fn talk (&self) {
        println!("Woof");
    }
}

impl Animal for Cow {
    fn talk (&self) {
        println!("Moo");
    }
}

Then I loop over a Vec<&Animal>, and polymorphism works well here:

fn main() {
    let animals: Vec<&Animal> = vec![&Dog, &Cow];
    for animal in &animals {
        animal.talk();
    }
}
// output:
// Woof
// Moo

So far so good. Now, I add an associated type Food to the trait (the type is not used, but it is only for the minimal repro).

struct Meat;
struct Herb;

trait Animal {
    type Food;
    ...
}
impl Animal for Dog {
    type Food = Meat;
    ...
}
impl Animal for Cow {
    type Food = Herb;
    ...
}

And now I get the error:

error[E0191]: the value of the associated type `Food` (from trait `Animal`) must be specified
   --> src/main.rs:188:23
163 |     type Food;
    |     ---------- `Food` defined here
...
188 |     let animals: Vec<&Animal> = vec![&Dog, &Cow];
    |                       ^^^^^^ help: specify the associated type: `Animal<Food = Type>`

But in this case, I cannot just follow the error message since the number of structs implementing the trait Animal is not supposed to be static.

What is the Rust way to solve that ? Thanks in advance


Solution

  • &Animal is short for &dyn Animal. dyn Animal is a trait object type, and it only exists for a given trait if the trait is object-safe. Traits with associated types are not object-safe, because dyn Animal cannot implement Animal without specifying the associated type Food

    This is an inherent limitation of runtime polymorphism (trait objects): you don't know the concrete type, so you can't know its associated type.²

    If you want to create a vector of things you can call .talk() on, it's easy enough to create a trait just for that (playground):

    trait Talk {
        fn talk(&self);
    }
    
    impl<A: Animal> Talk for A {
        fn talk(&self) {
            Animal::talk(self);
        }
    }
    
    let animals: Vec<&dyn Talk> = vec![&Dog, &Cow];
    

    You won't be able to write any code that uses Food via a &dyn Talk, which is the point: Food is dependent on the concrete type, and your vector contains multiple concrete types.

    See also


    ¹ You can make a trait object of types that all have the same associated types, like dyn Animal<Food = Herb>. This is commonly seen with Iterator, as in Box<dyn Iterator<Item = i32>>. But it does not solve the problem when the Animals have different kinds of Food.

    ² With compile-time polymorphism (generics), you can write code that is generic over anything that implements Animal with any Food type. But you can't put things of different compile-time types in a Vec, so that doesn't help either.