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 struct
s implementing the trait Animal
is not supposed to be static.
What is the Rust way to solve that ? Thanks in advance
&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.
¹ 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 Animal
s 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.