TL ; DR:
Polymorphism is when a single piece of code (like animal.speak()) does different things depending on which type the value actually is. You write the call once, and the right implementation runs automatically.
let animals: Vec<Box<dyn Animal>> = vec![Box::new(Dog), Box::new(Cat)];
for a in &animals {
a.speak(); // dispatches to Dog::speak then Cat::speak
}
Full lesson:
Polymorphism (Greek for "many forms") is what makes traits actually useful at scale. Once Dog and Cat both implement the Animal trait and provide speak(), you can stop caring which one you're holding. Any code that takes a dyn Animal and calls speak() works for both.
Why it matters
Imagine you're writing a function that prints what every animal in a zoo says:
fn roll(animals: &Vec<Box<dyn Animal>>) {
for a in animals { a.speak(); }
}
This function works for any current or future kind of animal. Add a Cow struct tomorrow — this loop runs Cow::speak() automatically, no edits needed. That's the win: code that works today and keeps working as you add more types.
Static type vs dynamic type
A Box<dyn Animal> is typed as Animal. The value it actually holds is a Dog, or a Cat, etc. Polymorphism is the language dispatching the method call based on the dynamic type (what it is at runtime) rather than the static type (what the box was declared to hold).
Notes by language
- Trait objects — dynamic dispatch happens only through a trait object such as
Box<dyn Animal>or&dyn Animal. No keyword likevirtualis needed. - Generics — a generic function such as
fn f<T: Animal>(a: &T)is resolved at compile time instead (static dispatch).
Your task
Animal, Dog, and Cat are already defined, each with its own speak. Inside main_function, build a Vec<Box<dyn Animal>> containing one Dog and one Cat, then loop through it calling speak() on each.
Expected output:
Woof
MeowFree plan: 30 tutor messages. PRO is unlimited.
