Here are the main themes that emerged from the Hacker News discussion on Rust error handling:
Overuse and "Magic" of Macros
A significant concern raised by multiple users is the perceived overuse of macros in Rust, leading to code that can feel like "magic" and be difficult to understand or trace. The argument is that while macros can reduce boilerplate, their pervasive use can obscure the underlying logic.
- "Iām getting a bit of a macro fatigue in Rust. In my humble opinion the less āmagicā you use in the codebase, the better." - jgilias
- "Please correct me if Iām misunderstanding this, but something that surprised me about Rust was how there wasnāt a guaranteed āpaper trailā for symbols found in a file... In general I just really donāt like the āmagicā of things being within scope or added to other things in a manner that itās not obvious." - Waterluvian
- "Yes. Macros are a hammer, but not everything is a nail." - burnt-resistor
- "Overuse of macros is a symptom of missing language capabilities." - quotemstr
- "Rust should seriously stop using macros for everything." - cute_boi
- "It appears that in this case, macros are a band-aid over missing features in the type system." - kaashif
- "Macros seem to be wrong in every language they're used, because people can't help themselves. It's like a red flag that the language designers were OK giving you enough rope to hang yourself with..." - andrewmcwatters
The Ideal Granularity of Error Types
A core debate revolves around how specific error types should be. Some users advocate for very fine-grained, per-function error types, seeing them as valuable documentation, while others find this approach leads to unwieldy hierarchies and duplication of error concepts across different functions.
- "Is it really though? Whatās the point of having an error type per function? As the user of std::io, I donāt particularly care if file couldnāt be read in function foo, bar, or baz, I just care that the file couldnāt be read." - jgilias
- "An error type per function doubles as documentation. If you treat all errors as the same it doesn't matter, but if you have to handle some, then you really care about what actual errors a function can return." - dwattttt
- "Ok, thatās a valid point! Though thereās a trade-off there, right? If both bar and baz can not find a file, theyāre both going to return their own FileNotFound error type. And then, if you care about handling files not being found somewhere up the stack, donāt you now have to care about two error types that both represent the same failure scenario?" - jgilias
- "I disagree that the status quo is āone error per module or per libraryā. I create one error type per function/action. I discovered this here on HN after an article I cannot find right now was posted. This means that each function only cares about its own error, and how to generate it. And doesnāt require macros. Just thiserror." - slau
- "That's the way, but I find it quite painful at time. Adding a new error variant to a function means I now have to travel up the hierarchy of its callers to handle it or add it to their error set as well." - thrance
- "The current standard for error handling, when writing a crate, is to define one error enum per module⦠Excuse me what? This means, that a function will return an error enum, containing error variants that the function cannot even produce. The same problem happens with exceptions." - metaltyphoon
- "The whole point (in my mind at least) of type safe errors is to know in advance all if the failure modes of a function. If you share an error enum across many functions, it no longer serves that purpose, as you have errors that exist in the type but are never returned by the function." - resonious
- "The author is a fan of Asterix I see :)" - kshri24 (referring to "indomitable nerds" who hold out against the standard)
Exceptions vs. Error Values
A recurring theme is the comparison of Rust's Result
type with traditional exception-based error handling found in languages like Java, Python, and C++. Some users lament Rust's choice to eschew exceptions, finding Result
to be more verbose or less elegant for certain use cases, while others defend Result
as being crucial for Rust's performance-oriented design.
- "My biggest disappointment in Rust (and probably my least popular opinion) is how Rust botched error handling. I think non-local flow control (i.e. exceptions) with automated causal chaining (like Python) is a good language design point and I think Rust departed from this good design point prematurely in a way that's damaged the language in unfixable ways." - quotemstr
- "Rust should have had only panics, and panic objects should have had rich contextual information, just like Java and Python." - quotemstr
- "It could have gone that way, but that would have āfattenedā the runtime and overhead of many operations, making rust unsuitable for some low-overhead-needed contexts that it chose to target as use-cases." - zbentley
- "In anything performance sensitive like OSes or games, C++ is compiled without exceptions. Unwinding is simply unacceptable overhead in the general case. Rust got errors right, with the possible exception of stdlib Error types." - dontlaugh
- "Table based unwinding is just one implementation choice. You can make other choices, some of which compile to code similar to error values. See Herb Sutter's deterministic exception proposal." - quotemstr
- "You're conflating a concept with one implementation of the concept and throwing away the whole concept." - quotemstr
- "This is eerily reminiscent of the discussions about Javaās checked exceptions circa 25 years ago." - layer8
- "Go got this right. Lua also has a nice error mechanism that I haven't seen elsewhere where you can explicitly state where in the call stack the error is occurring (did I error from the caller? or the callee?). Similarly, JavaScript seems to do OK, but I miss error levels. And C seems to also have OK error conventions that aren't too bad. There's a handful of them, and they're pretty uncontroversial." - andrewmcwatters
- "This article and comment section are making me feel like one of the only people that like error handling in Rust? I usually use an error for the crate or application with an enum of types. Maybe a more specific error if it makes sense. I don't even use anyhow or this error. I like it better then python and go." - tayo42
- "It's easy in hindsight to say that the error handling system that emerged in ~2020-ish should have been baked in the library when the language was stabilized in 2015. However even in 2012 (when Go 1.0 was released), errors as values was a pretty novel idea among mainstream programming languages and Go has some warts that were baked into the language that they have now given up on fixing." - nemothekid
Union Types and Type System Limitations
Several users expressed a desire for better support for union types or similar mechanisms in Rust's type system to simplify error handling and reduce boilerplate, drawing parallels with TypeScript's approach.
- "I feel like structural typing or anonymous sum types would solve this problem without macros. I mean given A = X | Y and B = X | Y | Z, surely the compiler can tell that every A is a B?" - kaashif
- "I really wish Rust had proper union types. So much ceremony over something that could be Foo | Bar | Error" - _benton
- "I don't understand the ambiguity. In TS, x is inferred as usize, second line is an error. In TS, x is inferred as usize | bool. Is there something specific to rust that makes this less clear that I'm missing?" - madeofpalk
- "I don't, and I say this as a long-time user of C++'s std::variant and boost::variant, which are effectively union types. Foo | Bar makes sense when Foo and Bar are logically similar and their primary difference is the difference in type. This is actually rather rare." - amluto
- "This is a mistake C++ has made and will probably pay for when it eventually tries to land pattern matching. Rust knows that Option
and Result are completely different semantically, likewise Result and Either communicate quite different intents even if the in-memory representations are identical." - tialaramex - "Yes. Macros are a hammer, but not everything is a nail." - burnt-resistor (This quote also fits here due to the idea that macros are being used to compensate for missing language features.)
- "When I need to parse a utf-8 error or something, I use .map_err(|_| ...)" - the__alchemist (This demonstrates a manual work-around that union types might solve.)
- "Well, I was hoping that this article implies a move towards something like TypeScript's approach to unions." - omegamancer
Standard Library vs. Third-Party Crates
There's a discussion about whether more robust error handling solutions, like those provided by thiserror
and anyhow
, should be integrated into Rust's standard library, versus relying on the ecosystem for innovation.
- "Every Rust project starts by looking into 3rd party libraries for error handling and async runtimes." - pjmlp
- "Or rather every rust project starts with cargo install tokio thiserror anyhow." - wongarsu
- "If we just added what 95% of projects are using to the standard library then the async runtime would be tokio, and error handling would be thiserror for making error types and anyhow for error handling." - wongarsu
- "Your ability to go look for new 3rd party libraries, as well as this article's recommendations, are examples of how Rust's careful approach to standard library additions allows the ecosystem to innovate and try to come up with new and better solutions that might not be API compatible with the status quo" - wongarsu
- "I prefer
derive_more
than thiserror. Because it is a superset and has more useful derive I use. color-eyre is better than anyhow." - jenadine - "I rather take the approach that basic language features are in the box. Too much innovation gets out of control, and might not be available every platform." - pjmlp
- "I think this is a valid criticism, however I think the direction Rust went was better. It's easy in hindsight to say that the error handling system that emerged in ~2020-ish should have been baked in the library when the language was stabilized in 2015." - nemothekid
- "I find the TS philosophy of requiring input types and inferring return types (something I was initially quite sceptical about when Flow was adopting it) quite nice to work with in practice - the same could be applied to strict typing of errors ala Effect.js?" - xixixao
Debugging and Maintainability Concerns
The complexity introduced by various error handling strategies directly impacts the ability to debug and maintain code. Issues arise from tracing errors through layers of abstraction, understanding macro-generated code, and managing the propagation of errors.
- "Itās a bit confusing sometimes with macros that create types that donāt seem to exist. But usually when I work with code I use an IDE anyway and āgo to definitionā will bring me to where itās defined, even when itās via a macro. Still generally prefer the plain non-macro declarations for structs and enums though because I can easily read them at a glance, unlike when āgo to definitionā brings me to some macro thing." - 57473m3n7Fur7h3
- "But it all breaks down when use of a library depends too much on magic code generation that cannot be inspected. And now we're back to dynamic language (Ruby/Python/JS) land with opaque, tinkering-hostile codebases that have baked-in complexity and side-effects." - burnt-resistor
- "Either Iām debugging macro errors or else Iām writing boilerplate trait impls all day⦠It feels like a lose/lose. I have yet to find a programming language that does errors well. :/" - throwaway894345
- "The author is a fan of Asterix I see :)" - kshri24 (This comment, while seemingly off-topic, implies a certain level of complexity in the error handling discourse.)
- "The whole point (in my mind at least) of type safe errors is to know in advance all if the failure modes of a function. If you share an error enum across many functions, it no longer serves that purpose, as you have errors that exist in the type but are never returned by the function." - resonious
- "With that scheme, what about propagating errors upwards? It seems like you have to wrap all "foreign" error types that happen during execution with an explicit enum type." - atombender
- "Maybe it would make sense to consider the API a function is presenting when making errors for it; if an error is related to an implementation detail, maybe it doesn't belong in the public API. If an error does relate to the public function's purpose (FileNotFound for a function that reads config), then it has a place there." - dwattttt
- "The point of the function signature is the interface for the calling function. If that function sees an error type with foo and bar and baz variants, it should have code paths for all of them." - devnullbrain