Vectors of Traits
Part of the Object Oriented Programming section of Coddy's Rust journey — lesson 46 of 61.
You've seen how Box<dyn Trait> lets a single variable hold different types. The real power emerges when you combine this with collections—storing multiple different types in a single vector.
A regular vector requires all elements to be the same type. You can't have a Vec<Dog> that also contains cats. But with trait objects, you can create a vector that holds anything implementing a shared trait:
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
struct Bird;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
impl Speak for Bird {
fn speak(&self) -> String { String::from("Tweet!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Bird),
];
}
The type Vec<Box<dyn Speak>> means "a vector of boxed trait objects." Each element is a Box pointing to something that implements Speak—the actual types can differ. You wrap each value with Box::new() when adding it to the vector.
This pattern is essential for building flexible systems where you need to manage collections of related but different objects—like UI components, game entities, or plugin systems.
Challenge
EasyLet's build a notification system that can handle different types of alerts! You'll create a collection that stores various notification types—emails, SMS messages, and push notifications—all in a single vector using trait objects.
You'll organize your code across two files:
notifications.rs: Define a publicNotifytrait with asendmethod that takes&selfand returns aStringdescribing the notification being sent. Then create three public structs:Email— with a publicrecipientfield (String). Itssendshould returnEmail to: {recipient}Sms— with a publicphonefield (String). Itssendshould returnSMS to: {phone}Push— with a publicdevicefield (String). Itssendshould returnPush to: {device}
main.rs: Bring in your notifications module and create a vector of typeVec<Box<dyn Notify>>that holds all three notification types. Use the inputs provided to create one of each notification type, add them to your vector in order (Email, Sms, Push), then print the result of callingsend()on the first element of the vector.
The key concept here is that despite Email, Sms, and Push being completely different structs, they can all live together in the same vector because they share the Notify trait. Each element is wrapped with Box::new() to create the trait object.
Your output should show the first notification:
Email to: {recipient}For example, with inputs alice@example.com, 555-1234, and iPhone-12:
Email to: alice@example.comYou will receive three inputs: the email recipient, the phone number, and the device name.
Cheat sheet
A Vec<Box<dyn Trait>> allows storing multiple different types in a single vector, as long as they all implement the same trait.
Regular vectors require all elements to be the same type, but trait objects enable heterogeneous collections:
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
];
}
Each element is wrapped with Box::new() when adding to the vector. The actual types can differ as long as they implement the shared trait.
Try it yourself
mod notifications;
use notifications::{Notify, Email, Sms, Push};
fn main() {
// Read inputs
let mut recipient = String::new();
std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
let recipient = recipient.trim().to_string();
let mut phone = String::new();
std::io::stdin().read_line(&mut phone).expect("Failed to read line");
let phone = phone.trim().to_string();
let mut device = String::new();
std::io::stdin().read_line(&mut device).expect("Failed to read line");
let device = device.trim().to_string();
// TODO: Create a Vec<Box<dyn Notify>> to hold different notification types
// TODO: Create Email, Sms, and Push instances using the inputs
// TODO: Add them to the vector in order (Email, Sms, Push) using Box::new()
// TODO: Print the result of calling send() on the first element
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Methods and Behavior
Intro Implementation BlocksThe Self ParameterMutable MethodsAssociated FunctionsMultiple Implementation BlocksMethod ChainingRecap - Rectangle Actions4Project: Virtual Pet
Defining the PetFeeding the Pet7Standard Traits
The Debug TraitThe Display TraitClone and CopyEquality TraitsRecap - Printable Point10Project: Document System
The Draw TraitText Component2Encapsulation and Modules
Modules BasicsThe Public KeywordPrivate FieldsGettersSettersRecap - Secure Locker5Generics
Generic StructsGeneric MethodsMultiple Generic TypesGeneric FunctionsRecap - Coordinate Point8Traits as Bounds
Trait Bounds SyntaxMultiple BoundsThe Where ClauseReturning Types with TraitsRecap - Generic Printer11Design Patterns in Rust
Newtype PatternCompositionThe Drop TraitFrom and IntoRecap - Smart Pointer Mock