Zero-cost abstractions are a key idea in Rust, meaning that abstractions (such as generic types, iterators, traits) should not incur any time or memory overhead compared to writing the equivalent code manually. In other words, high-level code, once optimized, compiles as efficiently as low-level code.
Rust achieves this through the mechanism of monomorphization: generic code is compiled for each specific type separately, without dynamic calls. Iterators and closures written on traits/generics are unfolded by the compiler into direct, strongly typed code, removing all unnecessary wrappers.
Example of a zero-cost iterator:
let v = vec![1,2,3]; let sum: i32 = v.iter().map(|x| x * 2).sum();
This snippet, after optimization, turns into nearly the same code as a manual loop:
let mut sum = 0; for x in &v { sum += x * 2; }
Does zero-cost abstraction in Rust mean that using trait objects (like &dyn Trait) will have no runtime overhead?
Answer: No! Runtime overhead occurs with dynamic method dispatch through vtable — when using dyn Trait instead of generic functions. Zero-cost is only achieved with static (monomorphized) generic abstractions.
Example:
trait Speaker { fn speak(&self); } fn say_twice<T: Speaker>(v: T) { v.speak(); v.speak(); } fn say_twice_dyn(v: &dyn Speaker) { v.speak(); v.speak(); } // The first call is monomorphized, the second is through vtable
Story
Story
Story
In an external library, a generic structure was created that was returned as Box<dyn Trait>. Despite the generic implementation, zero-cost was lost, as the abstraction was moved to runtime.