Library Errors vs Application Errors
Rule
Expose typed thiserror errors from reusable boundaries, use anyhow for application orchestration, and introduce miette only at a rich diagnostic presentation layer.
Why
Library callers need stable failures they can inspect, while applications need convenient propagation and final-boundary reporting.
Do
- Define a crate-local error enum and result alias for one cohesive library error surface.
- Keep public variants branch-oriented and stable.
- Use
anyhow::Resultin binaries, command handlers, workers, tests, and internal glue. - Keep typed domain errors in applications where callers recover differently.
- Use
miettefor labels, snippets, help text, and polished CLI reports. - Convert into
mietteonly at presentation; keep internal errors onthiserrororanyhow. - Follow error taxonomy and propagation for structure and sources.
Avoid
- Do not expose
anyhow::Errorfrom reusable public APIs. - Do not use
mietteas a general internal error type. - Do not mix application error-reporting stacks without a project reason.
- Do not default public APIs to
Box<dyn Error>. - Do not mirror every dependency error as a public variant.
Public API Notes
thiserror generates standard trait impls without appearing in signatures, but public variant fields and source types can still expose dependencies.
Example
#![allow(unused)]
fn main() {
// Reusable library boundary.
pub fn load(path: &Path) -> Result<Config, ConfigError> {
read_and_parse_config(path).map_err(ConfigError::from)
}
// Application orchestration boundary.
pub fn run(args: Args) -> anyhow::Result<()> {
run_application(args)
}
}
Exceptions
- Hand-write error impls to avoid a dependency or control public API tightly.
- Use
anyhowin an internal crate that is purely application implementation. - Use
miettewhen diagnostic output is part of the product experience.