Menu
Coddy logo textTech

Iterating Trait Objects

Part of the Object Oriented Programming section of Coddy's Rust journey — lesson 47 of 61.

Now that you can store different types in a Vec<Box<dyn Trait>>, the next step is processing that collection. Iterating over trait objects works just like iterating over any vector—you loop through each element and call methods defined by the trait.

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
        Box::new(Circle { radius: 1.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

When you iterate with for shape in &shapes, each shape is a reference to a Box<dyn Shape>. You can call any method from the Shape trait directly—Rust handles the dynamic dispatch automatically, calling the correct implementation for each concrete type.

This pattern is powerful for aggregating results. You could sum all areas, filter shapes by size, or transform the collection. The key insight is that once items are in a trait object collection, you interact with them purely through the trait's interface, regardless of their underlying types.

challenge icon

Challenge

Easy

Let's build a price calculator for a shopping cart that can hold different types of items! You'll create a system where products and services—each with different pricing logic—can be stored together and processed to calculate a total price.

You'll organize your code across two files:

  • items.rs: Define a public Priceable trait with a price method that takes &self and returns an f64. Then create two public structs:
    • Product — with public name (String) and cost (f64) fields. Its price should simply return the cost.
    • Service — with public name (String), hourly_rate (f64), and hours (f64) fields. Its price should return hourly_rate * hours.
  • main.rs: Bring in your items module and create a vector of type Vec<Box<dyn Priceable>>. Use the inputs to create one Product and one Service, add them to your vector, then iterate through the collection to calculate and print the total price of all items.

The power of trait objects shines here—your iteration code doesn't need to know whether it's processing a Product or a Service. It simply calls price() on each item, and Rust dispatches to the correct implementation automatically.

Your output should display the total price with one decimal place:

Total: ${total}

For example, with inputs Laptop, 999.99, Consulting, 150.0, and 3.0:

Total: $1449.99

And with inputs Book, 29.99, Tutoring, 50.0, and 2.0:

Total: $129.99

You will receive five inputs: the product name, product cost, service name, hourly rate, and hours worked (parse numeric values as f64).

Cheat sheet

To iterate over a collection of trait objects, use a standard for loop with a reference to the vector:

for item in &collection {
    item.trait_method();
}

When iterating with for item in &vec, each item is a reference to a Box<dyn Trait>. You can call any method from the trait directly—Rust handles dynamic dispatch automatically, calling the correct implementation for each concrete type.

Example of iterating over trait objects to aggregate results:

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

This pattern is useful for aggregating results—you can sum values, filter by criteria, or transform the collection while interacting with items purely through the trait's interface.

Try it yourself

mod items;

use items::{Priceable, Product, Service};

fn main() {
    // Read inputs
    let mut product_name = String::new();
    std::io::stdin().read_line(&mut product_name).expect("Failed to read line");
    let product_name = product_name.trim().to_string();

    let mut product_cost = String::new();
    std::io::stdin().read_line(&mut product_cost).expect("Failed to read line");
    let product_cost: f64 = product_cost.trim().parse().expect("Failed to parse");

    let mut service_name = String::new();
    std::io::stdin().read_line(&mut service_name).expect("Failed to read line");
    let service_name = service_name.trim().to_string();

    let mut hourly_rate = String::new();
    std::io::stdin().read_line(&mut hourly_rate).expect("Failed to read line");
    let hourly_rate: f64 = hourly_rate.trim().parse().expect("Failed to parse");

    let mut hours = String::new();
    std::io::stdin().read_line(&mut hours).expect("Failed to read line");
    let hours: f64 = hours.trim().parse().expect("Failed to parse");

    // TODO: Create a Product using product_name and product_cost

    // TODO: Create a Service using service_name, hourly_rate, and hours

    // TODO: Create a Vec<Box<dyn Priceable>> and add both items to it

    // TODO: Iterate through the vector and calculate the total price

    // TODO: Print the total with one decimal place
    // Format: Total: ${total:.1}
}
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