TL ; DR:
Put it all together: a trait, three concrete types that implement it, and a polymorphic loop that dispatches by runtime type. This is the shape of nearly every real OOP design.
trait Animal {
fn name(&self) -> &str;
fn sound(&self) -> &str; // each implementor picks
fn speak(&self) { // shared by all
println!("{}: {}", self.name(), self.sound());
}
}
// Dog, Cat, Bird each implement Animal
// let zoo: Vec<Box<dyn Animal>> = vec![Box::new(Dog { name: "Rex".to_string() }), ...];
// for a in &zoo { a.speak(); }
Full lesson:
This problem stitches together everything you've learned in this chapter:
- Shared contract — Rust has no inheritance, so the
Animaltrait plays the role of the common parent. - Structs —
Dog,CatandBirdeach hold aname: Stringfield. - Required methods — the trait declares
speak()with no body; each type must provide one. - Per-type behavior — each
impl Animal forblock prints its own sound. - Polymorphism — a
Vec<Box<dyn Animal>>holds a Dog, a Cat, and a Bird;speak()dispatches to the right implementation at runtime.
The shape of this code is the shape of nearly every real OOP design you'll see — a small contract at the top (the trait), concrete variants below, and the rest of the program written against the trait. New variants get added without touching the consumer code.
Your task
The Animal trait is already defined, with a single method speak(&self). Add three structs, each with a name: String field and an impl Animal for block — Dog (prints "Woof"), Cat (prints "Meow"), and Bird (prints "Tweet") after the name. Then in main_function, build a polymorphic collection holding one of each (Rex, Whiskers, Tweety in that order) and loop through it calling speak().
Expected output:
Rex: Woof
Whiskers: Meow
Tweety: TweetFree plan: 30 tutor messages. PRO is unlimited.
