Course contentsShow
← Rust
Lesson 529 of 1,9105. Type System and TraitsFree lesson

Trait Objects and Erasure

Understand the impact of type erasure on performance and memory layout.

In this lesson, you will learn how the performance and memory layout of your programs change when you trade static generics for dynamic trait objects.

The Trade-off of Erasure

When you use a generic function like fn process<T: Speak>(item: T), Rust performs static dispatch. At compile time, it generates a unique version of that function for every concrete type used. This is fast because the compiler knows exactly which code to run, but it can lead to "code bloat."

When you use a trait object, like &dyn Speak, you are engaging in type erasure. The compiler loses the specific information about the underlying type (e.g., whether it’s a Dog or a Cat). To compensate for this loss, Rust uses a wide pointer (or fat pointer) to keep track of the object.

The Fat Pointer

A &dyn Speak is actually two pointers packed together:

  1. Data pointer: Points to the actual instance (the Dog or Cat).
  2. Vtable pointer: Points to a "virtual method table," which is a generated lookup table containing the memory addresses of the trait's methods for that specific type.
trait Speak {
    fn say(&self);
}

// Memory Layout of &dyn Speak:
// [ Data Address | Vtable Address ]

Performance Implications

Because the compiler no longer knows the concrete type at compile time, it cannot "inline" the method calls. Instead, every time you call item.say(), the program must:

  1. Follow the Vtable pointer to find the method address.
  2. Jump to that address to execute the code.

This is called dynamic dispatch. While the performance cost of this single indirection is usually negligible in high-level applications, it prevents the compiler from optimizing across the call boundary and may hurt performance in tight loops or cache-sensitive code. Furthermore, because the size of the underlying type is erased, these objects must always be handled behind a pointer (like Box or &), as the compiler cannot allocate space on the stack for a type whose size is unknown.

Key Takeaway: Type erasure replaces compile-time knowledge with runtime pointer chasing, trading potential speed optimizations for flexible, heterogeneous collections.