Skip to content
Go back

Understanding impl Trait vs dyn Trait in Rust

Edit page

Multipart upload race condition

Understanding impl Trait vs dyn Trait in Rust

In Rust, you can write polymorphic function parameters in two main ways:

fn make_speak(speaker: impl Speak) {
    println!("Speak {}", speaker.speak())
}

fn make_speak_(speaker: &dyn Speak) {
    println!("Speak {}", speaker.speak())
}

Although both accept any type that implements Speak, they behave very differently.


Impl Speak — Static Dispatch (Generics)

The compiler generates a separate version of the function for each concrete type. This process is called monomorphization.

Example
struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String {
        "Woof".to_string()
    }
}

impl Speak for Cat {
    fn speak(&self) -> String {
        "Meow".to_string()
    }
}

make_speak(Dog);
make_speak(Cat);

The compiler generates something like:

make_speak_for_Dog(...)
make_speak_for_Cat(...)
Characteristics

&dyn Speak — Dynamic Dispatch (Trait Object)

fn make_speak_(speaker: &dyn Speak)

Here, the concrete type is erased at compile time. The function operates on a trait object, and method calls are resolved at runtime through a vtable.

Important: dyn Trait does not automatically mean heap allocation. It must be used behind a pointer (&, Box, Rc, etc.), but the data itself can live on the stack or heap depending on how it’s created.

Internally

&dyn Speak is a fat pointer containing:

Characteristics

Memory Difference

impl Speak
&dyn Speak

Conclusion:

Both impl Trait and dyn Trait enable polymorphism in Rust, but they serve different purposes.

impl Trait uses static dispatch, meaning the concrete type is known at compile time. This results in better performance and zero runtime overhead, but can increase binary size due to monomorphization. It’s ideal when performance matters and the type does not need to vary at runtime.

dyn Trait uses dynamic dispatch, where the concrete type is determined at runtime through a vtable. It enables true runtime polymorphism and allows storing different types together (e.g., in a Vec<Box>), but introduces a small runtime cost and must be used behind a pointer.

Reference:

https://en.wikipedia.org/wiki/Monomorphization https://doc.rust-lang.org/std/keyword.dyn.html


Edit page
Share this post on:

Next Post
Why Multipart File Uploads Break in Background Goroutines (Gin + Go)