Compare and contrast static and dynamic dispatch in Rust, explaining how each is achieved and their respective performance trade-offs.
Go & Rust interview question for Advanced practice.
Answer
Static and dynamic dispatch are two ways Rust resolves which function to call when using traits. Static Dispatch: Achieved via: Generics and trait bounds (monomorphization). When you write fn foo<T: MyTrait(item: &T), the compiler generates a specialized version of foo for each concrete type that foo is called with. Performance: This is extremely fast. The exact function to be called is known at compile time, so the compiler can inline the call, resulting in no runtime overhead. It's the same performance as calling a non-generic function directly. Trade-off: The main drawback is potential code bloat, as a copy of the function is created for each type, which can increase binary size. Dynamic Dispatch: Achieved via: Trait objects (e.g., &dyn MyTrait or Box<dyn MyTrait). A trait object is a 'fat pointer' containing a pointer to the data and a pointer to a vtable (virtual method table). Performance: This involves a small runtime cost. When a method is called on a trait object, there's an extra pointer dereference to find the vtable, and then another lookup within the vtable to find the correct method pointer to call. This prevents inlining and is slightly slower than a static call. Trade-off: The main benefit is flexibility. It allows you to have a heterogeneous collection of different types that all implement the same trait (e.g., Vec<Box<dyn Drawable), which is not possible with static dispatch.
Explanation
Rust's preference for static dispatch wherever possible is a key reason it can achieve C-like performance while offering high-level abstractions.