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
EasyLet'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 publicPriceabletrait with apricemethod that takes&selfand returns anf64. Then create two public structs:Product— with publicname(String) andcost(f64) fields. Itspriceshould simply return thecost.Service— with publicname(String),hourly_rate(f64), andhours(f64) fields. Itspriceshould returnhourly_rate * hours.
main.rs: Bring in your items module and create a vector of typeVec<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.99And with inputs Book, 29.99, Tutoring, 50.0, and 2.0:
Total: $129.99You 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}
}
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