In this lesson, you will learn how to choose between static and dynamic dispatch when returning errors to balance performance and flexibility.
The Problem: Monomorphization vs. Erasure
When you have a function that returns an error, you must decide how to represent that error type.
Static dispatch uses generics with trait bounds (e.g., T: std::error::Error). Because the compiler knows the exact concrete type at compile time, it performs "monomorphization," creating a specialized version of your function for that specific error. This is incredibly fast, but it forces your function signature to change if you ever decide to return a different error type.
Dynamic dispatch uses trait objects (e.g., Box<dyn std::error::Error>). The compiler "erases" the concrete type, wrapping it in a pointer. This adds a tiny runtime cost (a vtable lookup), but it offers massive flexibility: your function can now return any type that implements the Error trait, even if you decide to change the error type later without updating the signature.
Static vs. Dynamic in Practice
Consider these two approaches for returning an error:
// Static Dispatch: Fast, but rigid.
// Only works if the function returns one specific type.
fn process_static<E: std::error::Error>(val: i32) -> Result<(), E> {
todo!()
}
// Dynamic Dispatch: Flexible, but carries a small runtime cost.
// Can return any error that implements the trait.
fn process_dynamic(val: i32) -> Result<(), Box<dyn std::error::Error>> {
if val < 0 {
return Err("Negative value".into()); // String error
}
Ok(())
}
Which should you choose?
- Use Static Dispatch when writing high-performance libraries where the specific error type is part of your public API. It keeps your code predictable and ensures no heap allocation is required.
- Use Dynamic Dispatch for application-level logic where you are aggregating many different error types from various dependencies. It prevents "generics hell" and simplifies your code by allowing you to return a
Box<dyn Error>regardless of which internal logic failed.
Key Takeaway: Static dispatch gives you maximum performance via compiler-optimized generics, while dynamic dispatch provides the flexibility needed to group diverse error types under a single unified return type.