Menu
Coddy logo textTech

Dynamic Dispatch

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

When you use generics with trait bounds like <T: Summary>, Rust determines the exact type at compile time. This is called static dispatch—the compiler generates specialized code for each concrete type you use. It's fast, but there's a limitation: a variable can only hold one specific type.

What if you need a single variable that could hold different types at runtime? This is where trait objects come in. Using Box<dyn Trait>, you can store any type that implements the trait:

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 animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

The dyn keyword indicates dynamic dispatch—Rust looks up which method to call at runtime rather than compile time. The Box is necessary because trait objects don't have a known size; Box provides a pointer with a fixed size.

Think of it this way: generics say "I work with type T," while trait objects say "I work with anything that can do this." The tradeoff is a small runtime cost for the flexibility of handling different types through the same variable.

challenge icon

Challenge

Easy

Let's build a vehicle rental system that demonstrates the power of dynamic dispatch! You'll create a trait that defines what any rentable vehicle can do, then use Box<dyn Trait> to store different vehicle types in the same variable.

You'll organize your code across two files:

  • vehicle.rs: Define a public Rentable trait with a rental_info method that takes &self and returns a String. Then create two public structs:
    • Car — with a public model field (String). Its rental_info should return Car: {model}
    • Bike — with a public brand field (String). Its rental_info should return Bike: {brand}
  • main.rs: Bring in your vehicle module and use the inputs to demonstrate dynamic dispatch. Create a Box<dyn Rentable> variable that first holds a Car, print its rental info, then reassign the same variable to hold a Bike and print its rental info again.

The magic here is that a single variable of type Box<dyn Rentable> can hold either a Car or a Bike—Rust figures out which rental_info method to call at runtime. This is dynamic dispatch in action!

Your output should show both vehicles' information:

Car: {model}
Bike: {brand}

For example, with inputs Tesla Model 3 and Trek:

Car: Tesla Model 3
Bike: Trek

You will receive two inputs: the car model and the bike brand.

Try it yourself

mod vehicle;

use vehicle::{Rentable, Car, Bike};

fn main() {
    // Read input
    let mut car_model = String::new();
    std::io::stdin().read_line(&mut car_model).expect("Failed to read line");
    let car_model = car_model.trim().to_string();
    
    let mut bike_brand = String::new();
    std::io::stdin().read_line(&mut bike_brand).expect("Failed to read line");
    let bike_brand = bike_brand.trim().to_string();
    
    // TODO: Create a Box<dyn Rentable> variable that holds a Car
    // Print its rental info using the rental_info() method
    
    // TODO: Reassign the same variable to hold a Bike
    // Print its rental info again
    
}
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