Course contentsShow
← Rust
Lesson 984 of 1,9109. Smart Pointers and Interior MutabilityFree lesson

The Anatomy of a Trait Object

Analyze the vtable and data pointer structure behind boxed abstractions.

In this lesson, you will learn how Rust achieves dynamic dispatch by decomposing a trait object into a dual-pointer structure known as a wide pointer.

When you use a type like Box<dyn Trait>, you aren't just holding a reference to data on the heap. You are holding a fat pointer (also called a wide pointer) that is twice the size of a standard pointer. This structure consists of two distinct components: the data pointer and the vtable pointer.

The Anatomy of the Fat Pointer

  1. The Data Pointer: This points to the actual instance of the concrete type (e.g., a struct User) stored on the heap. Rust needs this to pass the self argument to methods.
  2. The Vtable Pointer: This points to a "virtual method table" (vtable). The vtable is a static structure generated by the compiler for every unique combination of a type and a trait. It contains the memory addresses of the function pointers for each method defined in the trait.

Visualizing the Structure

Consider a trait Speak and a struct Dog that implements it. When you perform let x: Box<dyn Speak> = Box::new(Dog);, the memory layout looks like this:

[ Data Pointer ] -> [ Dog Instance (Heap) ]
[ Vtable Pointer ] -> [ Vtable (Static Memory) ]
                        - Pointer to <Dog as Speak>::speak
                        - Pointer to drop glue (cleanup)
                        - Size/Alignment metadata

When you call x.speak(), the compiler does not know what type x holds at compile time. Instead, it performs a two-step lookup:

  1. It follows the vtable pointer to find the correct speak function implementation for the concrete Dog type.
  2. It calls that function, passing the data pointer as the self argument.

Because this lookup happens at runtime, we trade the static "inlining" optimizations we discussed in previous lessons for the flexibility of type erasure. While this indirection incurs a small performance cost—specifically the pointer chasing and the inability to inline the target function—it is the foundational mechanism that allows us to store heterogeneous collections of types that share the same behavior.

Key Takeaway: A trait object is a wide pointer composed of a data pointer to the instance and a vtable pointer to the function implementations, enabling runtime polymorphism via indirection.