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

Building Error Hierarchies

Organize large-scale project errors into hierarchical module structures.

In this lesson, you will learn how to structure complex, multi-layered error types to maintain clean abstractions in large Rust projects.

When your application grows beyond a few modules, having a single flat enum for all possible errors becomes unmanageable. The goal of building an error hierarchy is to mirror your project's architecture, allowing each module to define its own domain-specific errors while providing a clear path for them to bubble up.

Structuring by Domain

Instead of a monolithic AppError, break your errors into modules. A web service might define database::Error, auth::Error, and api::Error. Each module handles its internal failures independently.

// In src/database.rs
pub enum Error {
    ConnectionFailed(String),
    QueryTimeout,
}

// In src/api.rs
pub enum Error {
    InvalidPayload,
    Unauthorized,
}

Composing the Hierarchy

To represent the overall system, create a top-level ApplicationError enum that encompasses these domain errors. By implementing From or using specialized wrappers, you can convert internal errors into the higher-level domain type as they propagate.

pub enum AppError {
    Database(database::Error),
    Api(api::Error),
    Internal(String),
}

impl From<database::Error> for AppError {
    fn from(err: database::Error) -> Self {
        AppError::Database(err)
    }
}

By nesting errors this way, you create a "separation of concerns." The database module doesn't need to know about the api module; it only knows how to report its own failures. The top-level hierarchy acts as the bridge. This structure prevents you from leaking implementation details, keeps your error variants manageable, and makes pattern matching at the entry point of your application much more predictable.

When designing these hierarchies, always aim to make the errors at each level meaningful to the context in which they are caught. If a module’s internal error is too granular for the caller to handle, map it to a broader variant before passing it up the stack. This maintains the "clean interface" boundary while preserving the diagnostic information required for debugging.

Key Takeaway: Organize errors into modular hierarchies that reflect your project structure, using From conversions to seamlessly bridge domain-specific errors into high-level application states.