Trying to Get Error Backtraces in Rust Libraries Right
Key topics
The quest for reliable error backtraces in Rust libraries has sparked a lively debate about the language's error handling best practices. Commenters lament that these best practices are scattered across obscure blog posts, making it tough for developers to stay up-to-date, with some suggesting that a unified resource, like Python's pep8, would be a game-changer. While some argue that Rust is pioneering error handling without exceptions, others point out that the language is not alone in this challenge, and that the industry as a whole has been slacking on this front. As the discussion unfolds, it becomes clear that the issue is complex, with some attributing the problem to the lack of standardization around Rust's Error type.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
3d
Peak period
38
84-96h
Avg / period
10.3
Based on 62 loaded comments
Key moments
- 01Story posted
Aug 26, 2025 at 6:02 PM EDT
4 months ago
Step 01 - 02First comment
Aug 30, 2025 at 4:00 AM EDT
3d after posting
Step 02 - 03Peak activity
38 comments in 84-96h
Hottest window of the conversation
Step 03 - 04Latest activity
Sep 2, 2025 at 12:47 AM EDT
4 months ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
And even then, there's no guarantee what you find is not outdated.
A central dogma approach just means you need more doublethink, the "best practices" still gradually change but now you're pretending they've always been this way even as they shift beneath your feet.
eg: if you want distributed systems, esp Java-style, https://www.martinfowler.com is a pretty handy reference. (similar for other areas of SWE).
It’s nice to have a single resource that documents a bunch of non-obvious but still-generally-accepted engineering practices relevant to a domain. It’s of course an opinion, but is reasonably uncontroversial. Or at least, you won’t go too wrong while you develop your own taste in said domain.
I get what poster means.
Everytime I come back to rust I find it hard to know what the latest thing and way to do something is.
The entire software development industry has slept on error handling without exceptions and Rust developers are the first ones to actually start addressing the problems.
This problem should have been solved decades ago by C/C++ developers.
Java e.g. would benefit from a strict null mode, but the legions of half baked ‘engineers’ wouldn’t comprehend how to write software when you can’t initialize a reference to null and only set it later.
But, neither of these is intended as "the" error type - unless you're a formatter or an I/O abstraction they're not the error types you want.
Rust provides a trait core::error::Error (aka std::error::Error) and that might be what you're thinking about. It's recommended that your own private error type or types should implement this trait but nothing requires this in most contexts.
For misc Rust engineering, like the OP, I agree it’s quite scattered. I personally like to save good ones in my Feedbin as I encounter them
I would actually strengthen this to: «The core issue with Rust is that Rust still hasn’t stabilized _»
C++ had this unhealthy commitment to stabilization and it meant that their "Don't pay for what you don't use" doctrine has always had so many asterisks it's practically Zalgo text or like you're reading an advert for prescription medicine. You're paying for that stabilization, everywhere, all the time, and too bad if you didn't need it.
Unwinding stabilizations you regret is a lot more work even in Rust, consider the Range types, landing improved Range types is considerable work and then they'll need an Edition to change which types you get for the syntax sugar like 1..=10
Or an example which happened a long time ago, the associated constants. There are whole stdlib sub-packages in Rust which exist either primarily or entirely as a place for constants of a type, std::f32::MAX is just f32::MAX but the associated constant f32::MAX only finally stabilized in 1.43 so older code uses std::f32::MAX nevertheless if you learned to write std::f32::MAX you may only finally get deprecation messages years from now and they're stuck with keeping the alias forever of course because it's stable.
Not to mention Rust also allocates dynamic objects on the heap. So not sure what is your point.
My point is runtime overhead. In C# structs and their refs (including a simple borrow checker to detect invalid ref use) were introduced to escape GC management und reduce it's runtime impact on the programs
But in languages with exceptions, if you want to know how a function can fail, you have two options:
- Hope the documentation is correct (it isn’t)
- Read the body of the function and every function it calls
Reasonable people can disagree on the right approach here, but I know which I prefer.
Or maybe you have 100% path coverage in your test..
Knowing what kind of errors can occur is one of those tools.
But often some "expected" errors can be handled in some way better (retry, ask user, use alternate approach, ...)
Almost every function can fail with OutOfMemoryError and you can't do anything about it.
I've accepted that everything can fail. Just write code and expect it to throw. Write programs and expect them to abort.
I don't understand this obsession with error values. I remember when C++ designers claimed, that exceptions provide faster code execution for happy path, so even for systems language they should be preferred. Go error handling is bad. Rust error handling is bad. C error handling is bad, but at least that's understandable.
Why? Let's say you've opened a memory mapped file, you've got pointer, and hand this pointer down to some library - "Here work there" - the library thinks - oh, it's normal memory - fine! And then - physical block error happens (whether it's Windows, OSX, Linux, etc.) - and now you have to handle this from... a rather large distance - where "error code" handling is not enough - and you have to use signal handling with SIGxxx or Windows SEH handling, or whatever the OS provides
And then you have languages like GoLang/Rust/others where this is a pain point (yes you can handle it), but how well?
If you look in ReactOS the code is full with `__try/__except` - https://github.com/search?q=repo%3Areactos%2Freactos+_SEH2_T... - because user provided memory HAVE to be checked - you don't want exception happening at the kernel reading bad user memory.
So it's all good and fine, until you have to face this problem... Or decide to not use mmap files (is this even possible?).
Okay, I know it's just a silly little thing I'm pointing here - but I don't know of any good solution off hand...
And even handling this in C/C++ with all SEH capabilities - it still sucks...
In user-space, memory overcommit means that we will almost or literally never see an out of memory error.
In kernel space and other constrained environments, we can simply check for allocation, failure and handle it accordingly.
This is a terrible argument for treating all code as potentially failing with any possible error condition.
Only if you control the entire ecosystem, from application to every library you're ever going to use. And will not work for library code anyway.
> In user-space, memory overcommit means that we will almost or literally never see an out of memory error.
Have you ever deployed an application to production? Where it runs in the container, with memory limits. Or just good old ulimit. Or any language with VM or GC which provides explicit knobs to limit heap size (and these knobs are actually used by deployers).
> This is a terrible argument for treating all code as potentially failing with any possible error condition.
This is reality. Some programs can ignore it, hand-waving possibilities out, expecting that it won't happen and it's not your problem. That's one approach, I guess.
Two edge cases existing is a terrible argument for creating a world in which any possible edge case must also be accounted for.
>> This is a terrible argument for treating all code as potentially failing with any possible error condition.
> This is reality. Some programs can ignore it, hand-waving possibilities out, expecting that it won't happen and it's not your problem. That's one approach, I guess.
No, it’s not reality. It’s the mess you create when you work in languages with untyped exception handling and with people that insist on writing code the way you suggest.
> Almost every function can fail with OutOfMemoryError and you can't do anything about it.
In fact we can - though rarely do - prove software does not have either of these mistakes. We can bound stack usage via analysis, we usually don't but it's possible.
And avoiding OOM is such a widespread concern that Rust-for-Linux deliberately makes all allocating calls explicitly fallible or offers strategies like Vec::push_within_capacity a method which, if it succeeds pushes the object into the collection, but, if it's full rather than allocate (which might fail) it gives back the object - no, you take it.
I'm not talking about embedded or kernels. Different stories.
But the best so far method I know.
I'm not trying to defend exceptions, nor checked ones, just trying to point out that I don't think they are the same.
For all intent and purposes I really liked Common Lisp's exception handling, in my opinion the perfect one ("restartable"), but it also comes with lots of runtime efficiency (and possibly other) cost (interoperability? safety (nowadays)...) - but it was valiant effort to make programmer better - e.g. iterate while developing, and while it's throwing exceptions at you, you keep writing/updating the code (while it's running), etc - probably not something modern day SRE/devops would want (heh "who taught live updating of code of running system is great idea :)" - I still do, but I can see someone from devops frowning - "This is not the version I've pushed"...)
It warns you if you ignore handling a Result because the type is annotated with must_use (which can be a compile error in CI if you choose to enforce 0 warnings). Not that this is true with try/catch - no one forces you to actually do anything with the error.
> What about errors (results) that came from deeper?
Same as with exceptions - either you handle it or propagate it up or ignore it.
real
compared to every exception-based language I’ve used, rust error handling is a dream. my one complaint is async, but tbh I don’t think exceptions would fare much better since things like the actor model just don’t really support error propagation in any meaningful way
I wonder how loudly "the community" would scream at me if I published something that just used panics for all error reporting.
It has its drawbacks, yes, but I'd never go back to the wild-west YOLO approach of unchecked exceptions, personally.
Honest question, because I can’t think of any. I can see it being advantageous to have checked-only exceptions but there has to be a good reason why it’s so rare-to-never that we see it.
I’m not sure how else you’d get the holy grail, which I’d define as:
1. The compiler enforces that you either handle an exception or pass it to the caller
2. Accurate and fine-grained stack traces on an error (built-in, not opt-in from some error library du jour)
3. (ideally) no runtime cost for non-exception paths (no branches checking for errors, exceptions are real hardware traps)
C++ has 2 and 3, Java has only 2 (because RuntimeException exists), Rust has only 1. I’d love a language with 1 and 2, but all 3 would be great.
And then Koka too, but I have zero experience...
Someon might shine light here about this...
Most of the articles start with "to fix the mistake we showed at the end of the last article", and end with "but now we've broken something else".
Needing to keep track of where exceptions can occur, so that you don't leave an operation half committed, sounds especially nasty: https://devblogs.microsoft.com/oldnewthing/20250827-00/?p=11...
I'm particularly mindful of C++ 26 Erroneous Behaviour for initialization. This idea was introduced for the forthcoming C++ 26 language version, it says that although just making a variable `int k;` and then later taking its value is an error, it's no longer Undefined Behaviour, the compiler shall arrange that it has some specific value.
This is a meaningful improvement over the C++ status quo. But, that doesn't mean the core idea is actually good. It's bad to do this in your language, they didn't have any better choice for C++ for historical reasons so this was the least bad option.
Rust tries to do this ergonomically with the ?-operator, which is not that easy in complex cases but reduces your cognitive load tremendously when possible.
- The definitive guide to error handling in Rust [0]
- Error Handling for Large Rust Projects - Best Practice in GreptimeDB [1]
- Designing error types in Rust [2]
- Modular Errors in Rust [3]
[0]: https://www.howtocodeit.com/articles/the-definitive-guide-to... [1]: https://greptime.com/blogs/2024-05-07-error-rust [2]: https://mmapped.blog/posts/12-rust-error-handling [3]: https://sabrinajewson.org/blog/errors