Menu
Coddy logo textTech

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 icon

Challenge

Easy

Let'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 public Notify trait with a send method that takes &self and returns a String describing the notification being sent. Then create three public structs:
    • Email — with a public recipient field (String). Its send should return Email to: {recipient}
    • Sms — with a public phone field (String). Its send should return SMS to: {phone}
    • Push — with a public device field (String). Its send should return Push to: {device}
  • main.rs: Bring in your notifications module and create a vector of type Vec<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 calling send() 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.com

You 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
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming