Course contentsShow
← Rust
Lesson 909 of 1,9107. Error Handling PatternsFree lesson

Dynamic vs Static Error Composition

Evaluate the architectural trade-offs of trait objects versus enum wrapping.

In this lesson, you will learn how to weigh the flexibility of Box<dyn Error> against the structural safety of custom error enums when composing failure states in your Rust applications.

The Architectural Choice

When your functions perform multiple sub-tasks, they often encounter different types of errors. You face a crossroads: do you use a static approach (a rigid enum that defines every possible error) or a dynamic approach (a trait object that hides the specific type)?

Static Error Composition (Enums)

Static composition uses enum wrappers. This is the idiomatic "Rust way," providing exhaustive matching and compile-time guarantees.

enum AppError {
    Io(std::io::Error),
    Network(reqwest::Error),
}
  • Pros: You know exactly what can go wrong. Consumers can use match to handle specific variants differently. It is memory-efficient and performant because the size is known at compile time.
  • Cons: It creates tight coupling. Every time you add a new error source, you must update the enum definition and all corresponding match arms.

Dynamic Error Composition (Trait Objects)

Dynamic composition uses Box<dyn Error + Send + Sync>. This effectively "erases" the specific error type, keeping only the knowledge that something failed and that it satisfies the Error trait.

fn perform_task() -> Result<(), Box<dyn std::error::Error>> {
    // Both IO and Network errors satisfy dyn Error
    let _ = std::fs::read("file")?;
    let _ = reqwest::blocking::get("url")?;
    Ok(())
}
  • Pros: High flexibility. You can return any error type without updating a central enum definition. This is excellent for rapid prototyping or library APIs where you don’t want to expose internal error variants.
  • Cons: You lose the ability to match on specific variants easily. It incurs a small heap allocation cost and loses the performance benefits of monomorphization.

Trade-off Summary

Choose static enums when your error domain is well-defined and callers need to perform recovery logic based on the type of failure. Choose dynamic trait objects when you are building a generic internal utility or a top-level application entry point where "bubbling up" the error is more important than handling the specific variant.

Key Takeaway: Static enums prioritize compile-time safety and recovery precision, while dynamic trait objects prioritize development velocity and loose coupling.