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.

Cheat sheet

Rust uses static dispatch with generics—the compiler generates specialized code for each type at compile time. This is fast but means a variable can only hold one specific type.

Trait objects enable dynamic dispatch, allowing a single variable to hold different types at runtime. Use Box<dyn Trait> to store any type implementing 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—method calls are resolved at runtime. Box is required because trait objects don't have a known size at compile time.

Key difference: Generics say "I work with type T" (one specific type), while trait objects say "I work with anything that implements this trait" (multiple types through the same variable). The tradeoff is a small runtime cost for flexibility.

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