Ramblings of an aging IT geek
← Ramblings of an aging IT geek
rust

what rust error handling felt like before anyhow turned up

A look back at hand-rolled error enums and the Error/From boilerplate that anyhow and thiserror quietly retired.

A code editor showing Rust source

It's easy to forget how much ceremony Rust error handling used to involve. These days you reach for anyhow in an application and thiserror in a library and the whole thing mostly disappears. It wasn't always like that, and I was reminded this week digging through an old project that predates both.

Before those crates, every binary grew its own bespoke error enum. You'd hand-write a variant per failure mode, then implement Display, then implement std::error::Error, then write a From impl for every foreign error type you wanted the ? operator to swallow. Miss one From and the compiler refused to convert, so ? wouldn't compile, so you wrote another impl.

enum AppError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
}

impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}

impl From<std::num::ParseIntError> for AppError {
    fn from(e: std::num::ParseIntError) -> Self { AppError::Parse(e) }
}

And that's before Display and Error. Multiply by every error type in a real program and you had pages of mechanical boilerplate that did nothing interesting and which you copied between projects.

There was failure for a while, which helped, and error-chain before that, which I never warmed to. anyhow and thiserror are the pair that finally got the ergonomics right: one for "I just want this to bubble up with context", one for "I'm a library and my callers need to match on the variants". The split is exactly the right one, and the boilerplate above collapses into a derive and a ?. Worth remembering what it replaced.