In this lesson, you will learn how Rust’s trait system allows you to write high-level, expressive code without sacrificing execution speed or binary size.
Static Dispatch: The Secret to Zero-Cost
When you use a generic function with a trait bound like fn process<T: Summary>(item: T), Rust uses static dispatch. During compilation, the compiler looks at every place you call process and generates a concrete version of that function for the specific type you passed in.
Because the compiler knows the exact type at compile time, it can "inline" the trait method code directly into your function. This removes the overhead of a function call and gives the optimizer a clear view to perform further improvements, such as loop unrolling or constant folding. The resulting machine code is often identical to what you would have written if you had manually called the non-generic function for that type.
trait Speak {
fn say(&self);
}
struct Dog;
impl Speak for Dog { fn say(&self) { println!("Woof!"); } }
// Because of static dispatch, the compiler generates:
// fn process_dog(item: Dog) { item.say(); }
fn process<T: Speak>(item: T) {
item.say();
}
The Cost of Abstraction
"Zero-cost" does not mean "free" in every dimension. While static dispatch eliminates runtime overhead (speed), it causes the compiler to generate a unique copy of the function for every distinct type you use. This process, known as monomorphization, can lead to "binary bloat," where your executable grows larger as you add more generic implementations.
Conversely, if you opt for Dynamic Dispatch (using trait objects like &dyn Speak), you trade that speed for binary size. A dyn object uses a "vtable" (virtual method table) to look up the correct function at runtime. This allows the compiler to generate the function code only once, keeping your binary smaller but adding a small, non-zero cost to every method call due to the pointer indirection.
Choosing between them is a trade-off: use generics for peak execution performance, and use trait objects if you need to minimize binary size or handle mixed collections of types.
Key Takeaway: Static dispatch via trait bounds allows the compiler to eliminate abstraction overhead entirely, resulting in machine code as fast as hand-written, type-specific logic.