Rust Errors Without Dependencies
Key topics
Diving into the nuances of error handling in Rust, a recent blog post sparked a lively debate about the trade-offs between explicit error management and the convenience of built-in language features. Commenters weighed in on the verbosity of Rust's approach, with some arguing it's minimal compared to the custom error handling macros found in C projects, while others lamented the amount of boilerplate code required. A notable discussion emerged around the Cloudflare outage caused by an unwrap failure, with some developers pointing out that Rust's goal is to eliminate memory corruption, not crashes, and that safely crashing can be a valid mechanism for achieving memory safety. As the conversation unfolded, a consensus formed around the idea that Rust's explicit error handling, although verbose, provides a valuable level of control and reliability.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
16h
Peak period
51
12-18h
Avg / period
14.8
Based on 89 loaded comments
Key moments
- 01Story posted
Dec 27, 2025 at 11:12 PM EST
12 days ago
Step 01 - 02First comment
Dec 28, 2025 at 3:33 PM EST
16h after posting
Step 02 - 03Peak activity
51 comments in 12-18h
Hottest window of the conversation
Step 03 - 04Latest activity
Dec 30, 2025 at 7:14 PM EST
9 days 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.
"Trusted" is a different category from "valid" for a reason. Especially if you're working in a compiled language on something as important as that, anything that isn't either part of the code itself or in a format where literally every byte sequence is acceptable, should be treated as potentially malformed. There is nothing compiling the config file.
If I was designing a language to surpass Rust, I'd make panics opt-in. I think Rust has a team looking into no-panic but it's a funny loophole for a language that wanted to eliminate crashes.
It's really not fair to compare these when most of the errors of one language are caught at compile time by the other.
It reminds me of that scene from silicon valley "Anything related to errors sounds like your area
https://youtu.be/oyVksFviJVE?si=NVq9xjd1uCnhZkPz&t=55
Can we not just agree that interpreted languages (save the Ackshually) like python and node need a more elaborate error handling system because they have more errors than compiled languages? It's not a holy war thing, I'm not on either side, in fact I use interpreted languages more than compiled languages, but it's just one of the very well-known trade-offs.
Yes, that's precisely what I meant about "trade-offs that Rust clearly wanted to avoid".
And this is basically why I like the C/C++ model of not having a centralized repo better. If I need some external piece of software, I simply download the headers and/or sources directly and place them in my project and never touch these dependencies again. Unless somehow these are compromised at the time of download, I will never have to worry about them again. Also these days I am increasingly relying on LLMs to simply generate what I need from scratch and rely less and less on external code.
I’m very tempted to go this direction myself with Rust, vendoring in and “maintaining” (using Claude Code to maintain) dependencies. or writing subsets of the crates I need myself and using those. the sprawl with Rust dependencies is concerning
I think you’re conflating the tool, with how people manage deps.
https://doc.rust-lang.org/cargo/commands/cargo-vendor.html
But this is exactly what rust does x) `cargo add some_crate` adds a line `crate_name = "1.2.3"` to your project config, downloading and pinning the dependency to that exact version. It will not change unless you specifically change it.
The claim is just that `cargo add crate` is functionally identical to downloading a C++ header and keeping it in the same version, since in both cases the dependency will be pinned to that fixed version.
You can vendor deps with cargo if you want but fighting cmake/make/autoconf/configure/automake build spaghetti is not my idea of a good time.
As it is any moderately large Rust project ends up including several different error handling crates.
I also feel thiserror encourages a public error enum which to me is an anti-pattern as they are usually tied to your implementation and hard to add context, especially if you have a variants for other error types.
IMO Rust should provide something like thiserror for libraries, and also something like anyhow for applications. Maybe we can't design a perfect error library yet, but we can do waaay better than nothing. Something that covers 99% of uses would still be very useful, and there's plenty of precedent for that in the standard library.
It's very rare that `pub enum Error { ... }` is something I'd put into the public API of a library. epage is absolutely correct that it is an extensibility hazard. But having a sub-ordinate "kind" error enum is totally fine (assuming you mark it `#[non_exhaustive]`).
It's not necessarily about frequency, but about extensibility. There's lot of grey area there. If you're very certain of your error domain and what kinds of details you want to offer, then the downside of a less extensible error type may never actualize. Similarly, if you're more open to more frequent semver incompatible releases, then the downside also may never actualize.
I mean I don't see the difference between having the non-exhaustive enum at the top level vs in a subordinate 'kind'.
- Struct variant fields are public, limiting how you evolve the fields and types
- Struct variants need non_exhaustive
- It shows using `from` on an error. What happens if you want to include more context? Or change your impl which can change the source error type
None of this is syntactically unique to errors. This becomes people's first thought of what to do and libraries like thiserror make it easy and showcase it in their docs.
But the whole point of thiserror style errors is to make the errors part of your public API. This is no different to having a normal struct (not error related) as part of your public API is it?
> Struct variants need non_exhaustive
Can't you just add that tag? I dunno, I've never actually used thiserror.
Your third point makes sense though.
> But the whole point of thiserror style errors is to make the errors part of your public API. This is no different to having a normal struct (not error related) as part of your public API is it?
Likely you should use private fields with public accessors so you can evolve it.
Eventually, the JDK did add a logging facility [1]... but too little, too late: nobody uses that and any library that uses logging will probably forever use slf4j.
[1] https://docs.oracle.com/en/java/javase/11/core/java-logging-...
The pros for using anyhow are big: Easily stack errors together - eg file.open().context(path) -, errors are easily kept up to date, and easy to find where they occur.
An enum error is pleasing when you're finished, but cumbersome in practice.
It's niche situation that you need to write a function that exposes a meaningful Error state the caller can branch on. If the return state is meaningful, you usually don't put it in the Err part. eg parsers using { Ok,Incomplete,Error }. IMO Error enums are best for encoding known external behavior like IO errors.
For example: The Sender.send_deadline returning { Timeout, Closed } is the exception in being useful. Most errors are like a Base64 error enums. Branching on the detail is useless for 99.99% of callers.
i.e. If your crate is meant for >1000 users, build the full enum.
For any other stuff, use anyhow.
anyhow seems useful in the very top layers where you basically just want bubble the useful errors modeled elsewhere to a top layer that can appropriately handle them. I don't think a crate should abdicate from modeling the error domain any more than they should abdicate from modeling the other types.
I'm not saying the error domain is small per se.
Instead, one argument I'm making: what you're describing about errors bubbling up to the top layer, is what happens with the overwhelming majority of errors in my experience.
Whether the error space is large or small, just wait until you have an immediate need to treat one error different from the rest. It happens, it's just not common.
I didn't steelman the case for when to use enums, but in short: In a tiny error space or as valuable documentation.
(The doc value is undermined somewhat when projects use 1-big-error and functions that eg only returns 3 of the 6 possible errors)
I'm not advocating removing an Error enum. Just that writing one can and should be postponed, saving a lot of maintenance edits.
Yes, it's just harder. We usually have a pretty good idea what callers want from the happy path, but the range of things that callers may or may not want to do in case of an error is very broad.
This is very hand-wavy, but I think we're talking at a very high level of abstraction here. My main point is to suggest that there is more of a balancing act here than perhaps your words suggest.
But that's exactly what you do when you...
>... try to indicate the different types of errors that could occur and what type of information needs to be included to be useful to the caller.
Modelling the error domain means to decide which possible distinctions matter and which don't. This is not self evident. It's you imgining what users may want to do with your errors.
Is it enough for the caller to know that some string you're parsing is invalid or do they need to know what it is exactly that makes it invalid?
If you decide to just pass on everything you happen to know based on your current implementation then you are putting restrictions on future changes to that implementation, potentially including the use of third party libraries.
What if you find a shortcut that makes your parser 10 times faster but doesn't provide this detailed information?
What I see a lot in Rust is that massive amounts of implementation details are leaked via errors. thiserror actually encourages this sort of implementation leak:
#[derive(Error, Debug)] pub enum MyError { Io(#[from] io::Error), Glob(#[from] globset::Error), }
Your example is interesting actually because there are real differences in those types of errors. IO errors are different from the globset errors. It is reasonable to assume that you would want to handle them differently. If your function can actually have both errors as a consumer I would want to know that so I can make the correct decisions in context. If you don't have a way to signal to my code that both happen you've deprived me of the tools to do my own proper error modeling and handling.
You are implying that there is one correct and self evident set of distinctions. I disagree with that. In library design, you're always making assumptions about what users may want.
>Your example is interesting actually because there are real differences in those types of errors. IO errors are different from the globset errors.
Of course. I'm not complaining about the distinction between io errors and globbing errors here but about the fact that the globset library and its specific error type is leaked.
What if someone (say fastburningsushi) comes along and creates a glob library that absolutely smokes this one? :P
Also, database errors. While the specific error may not be important, knowing whether an error means that a transaction definitely did not commit is valid, as is knowing whether retrying the transaction is likely to be useful. (The common case is retrying transactions that failed due to deadlock.)
Sure, it’s slightly more error prone than proper enum errors, but it’s so much less friction, and much better than just doing panic (or unwrap) everywhere.
It just felt like too much churn and each one offered barely any distinction to the previous.
Additionally, the `std::error::Error` trait was very poorly designed when it was initially created. It was `std` only and linked to a concept of backtraces, which made it a non-started for embedded. It just seemed to me that it was a bad idea ever to use it in a library and that it would harm embedded users.
And the upside for non-embedded users was minimal. Indeed most of it's interface since then has been deprecated and removed, and it to this day has no built-in idea of "error accumulation". I really can't understand this. That's one of the main things that I would have wanted an generic error interface to solve in order to be actually useful.
It was also extremely painful 5 years ago when cargo didn't properly do feature unification separately for build dependencies vs. target dependencies. This meant that if you used anyhting in your build.rs that depended on `failure` with default features, and turned on `std` feature, then you cannot use `failure` anywhere in your actual target or you will get `std` feature and then your build will break.
I think the whole "rust error handling research" area has frankly been an enormous disappointment. Nowadays I try to avoid all of these libraries (failure, anyhow, thiserror, etc.) because they all get abandoned sooner or later, and they brought very little to the table other than being declared "idiomatic" by the illuminati. Why waste my time rewriting it in a year or two for the new cool flavor of suck.
Usually what I actually do in rust for errors now is, the error is an enum, and I use `displaydoc` to make it implement `Display`, because that is actually very simple and well-scoped. I don't bother with implementing `std::error::Error`, because it's pointless.
If I'm writing an application and I come to a point where I need to "box" or "type erase" the error, then it becomes `String` or perhaps `Box<str>` if I care about a few bytes. It may feel crude, but it is simple and it works. That doesn't let you downcast errors later, but the situations where you actually have to do that are very rare and I'm willing to do something ad hoc in those cases. You can also often refactor so that you don't actually have to do that. I'm kind of in the downcasting-is-a-code-smell camp anyways.
I'm a little bit excited about `rootcause` because it seems better thought out than it's progenitors. But I have yet to try to make systematic use of it in a bigger project.
It was never linked to backtraces. And if you used `std::error::Error` in a library that you also wanted to support in no-std mode, then you just didn't implement the `std::error::Error` trait when the `std` feature for that library isn't enabled. Nowadays, you can just implement the `core::error::Error` trait unconditionally.
As for backtrace functionality, that is on the cusp of being stabilized via a generic interface that allows `core::error::Error` to be defined in `core`: https://github.com/rust-lang/rust/issues/99301
> and it to this day has no built-in idea of "error accumulation".
The `Error` trait has always had this. It started with `Error::cause`. That was deprecated long ago because of an API bug and replaced with `Error::source`.
> It just felt like too much churn and each one offered barely any distinction to the previous.
I wrote about how to do error handling without libraries literally the day Rust 1.0 was published: https://burntsushi.net/rust-error-handling/
That blog did include a recommendation for `failure` at one point, and now `anyhow`, but it's more of a footnote. The blog shows how to do error handling without any dependencies at all. You didn't have to jump on the error library treadmill. (Although I will say that `anyhow` and `thiserror` have been around for a number of years now and shows no signs of going away.)
> I don't bother with implementing `std::error::Error`, because it's pointless.
It's not. `std::error::Error` is what lets you provide an error chain. And soon it will be what you can extract a backtrace from.
> I'm kind of in the downcasting-is-a-code-smell camp anyways.
I happily downcast in ripgrep: https://github.com/BurntSushi/ripgrep/blob/0a88cccd5188074de...
That also shows the utility of an error chain.
The tree allows you to say e.g. this function failed because n distinct preconditions failed, all of which are interesting, and might have lower level details. Or, I tried to do X which failed, and the fallback also failed. The error chain thing doesn’t capture either of these semantics properly.
Check out `rootcause` which is the first one I’ve seen to actually try to do this.
I’ll respond to the backtrace comments shortly.
It's correct to say that `std::error::Error` does not support a tree of errors. But it is incorrect to say what you said: that it's pointless and doesn't allow error accumulation. It's not pointless and it does provide error accumulation. Saying it doesn't is a broad overstatement when what you actually mean is something more precise, narrow and niche.
I do think that `std::error::Error` is mostly pointless. That's a value judgment, and reasonable people can disagree.
I'll list a number of things that I've experienced coworkers being confused about around the `std::error::Error` trait.
1) Why does it require `Display` and then not use it?
2) Displaying it is very simple: `format!("{err}")`. If you want to format the error and it's chain of causes, actually using the `std::error::Error` functionality, the recommended way was to use yet another experimental `error_chain` library. When should we actually do that? When is that appropriate?
Now we have a place where there's two different ways to do the same thing (display an error). Additionally there is controversy and churn around it.
In a large project, most developers will be completely ignorant about the second more obscure possibility. And in most projects, you don't really need two ways to format an error. So I tend to do the friendliest thing for developers. There is only one way, and it is Display, which 100% of rust developers know about, and I avoid using `std::error::Error`.
I understand that there's a bright shiny future that people hope it's headed for, where everything around `std::error::Error` is easy and obvious, and we have powerful flexible expressive ergonomic error handling. I was excited about that like 7 years ago, now I just kinda want to change the channel.
I took it as a statement of fact. It is a factual matter of whether `std::error::Error` has a point to it or not. And it definitively does. I use the error chain in just about every CLI application I've built. It's not even mostly pointless. It's incredibly useful and it provides an interoperable point for libraries to agree upon a single error interface.
And one `Error::provide` is stable, it will be even more useful.
> I've tried to argue that, it can create bigger problems then it solves. It's a trait that only exists on platforms with `std`. That itself is pretty nasty and if you care at all about platforms that aren't like that, you're taking on a lot of build complexity. If you really need this why not just make your own `trait HasCause` which is like a subset of `std::error::Error` functionality, and simply doesn't require `std`?
The `Error` trait has been in `core` for about a year now. So you don't need any build complexity for it.
But you're also talking to someone who does take on the build complexity to make `std::error::Error` trait implementations only available on `std`. (Eventually this complexity will disappear once the MSRV of my library crates is new enough to cover `core::error::Error`.) But... there really isn't that much complexity to it? It's a very common pattern in the ecosystem and I think your words are dramatically overstating the work required here.
> 1) Why does it require `Display` and then not use it?
Because it defines the contract of what an "error" is. Part of that contract is that some kind of message can be generated. If it didn't require `Display`, then it would have to provide its own means for generating a message. It's not a matter of whether it's "used" or not. It's defining a _promise_.
> 2) Displaying it is very simple: `format!("{err}")`. If you want to format the error and it's chain of causes, actually using the `std::error::Error` functionality, the recommended way was to use yet another experimental `error_chain` library. When should we actually do that? When is that appropriate?
Who says it was "the recommended way"? I never recommended `error_chain`.
Writing the code to format the full chain is nearly trivial. I usually use `anyhow` to do that for me, but I've also written it myself when I'm not using `anyhow`.
> Now we have a place where there's two different ways to do the same thing (display an error). Additionally there is controversy and churn around it.
Yes, this is a problem. If something appears in the `Display` of your error type, then it shouldn't also appear in your `Error::source`. This is definitely a risk of getting this wrong if you're writing your error type out by hand. If you're using a library like `thiserror`, then it's much less likely.
> I understand that there's a bright shiny future that people hope it's headed for, where everything around `std::error::Error` is easy and obvious, and we have powerful flexible expressive ergonomic error handling. I was excited about that like 7 years ago, now I just kinda want to change the channel. I'm glad some people still find some benefit in the small improvements that have occurred over time... and I hope in 8 more years there's more to the story than where we are today.
I was very happy with error handling in Rust at 1.0 personally.
I think people got burned by the churn of the error library treadmill. But you didn't have to get on that treadmill. I think a lot of people did because they overstate the costs of write-once boiler plate and understate the costs of picking the wrong foundation for errors.
Okay, I'm willing to give you this one. I haven't encountered this view among coworkers before. The consensus view in my circles was, there is potentially a very small upside if your consumer is willing to do the error chain song and dance, but most people don't, and you have a risk of a lot of complexity for no_std builds, so it's better to avoid.
> The `Error` trait has been in `core` for about a year now. So you don't need any build complexity for it.
I actually had no idea that it has been in `core for a year now!!
I am very happy, this means that the situation has actually improved dramatically and there's no major downside to using `std::error::Error`.
I'm going to re-evaluate my choices. I have a lot of code that systematically avoids `std::error::Error`, and I'm not sure it's worth it to change it all, but there's probably no good reason to avoid it if it's in core now.
---
I think you are mistaken, however, about backtraces never being a part of std::error::Error. There are RFC's from 2018 years ago that talk all about it:
https://rust-lang.github.io/rfcs/2504-fix-error.html
Here's a withoutboats PR in 2020: https://github.com/rust-lang/rust/pull/72981
https://github.com/rust-lang/rust/pull/72981#issuecomment-66...
It may just be a matter of perspective -- if you don't count rust pre 1.0, then yeah it "never" involved backtrace. But in my company, we were trying to use rust even at that time for no_std targets, and trying to figure out where the puck was headed on std::error::Error was complicated. All the backtrace stuff made us think, maybe this is just not meant for us and we should rip it out, and we did eventually, although not without a lot of internal arguments.
> It's incredibly useful and it provides an interoperable point for libraries to agree upon a single error interface. > > And one `Error::provide` is stable, it will be even more useful.
Now it's clear to you that the "point" of it is error chains, that was maybe not a consensus view of the libs team on the day of 1.0.
Because if the only purpose was error chains, it could have been in core on the day of rust 1.0, as it is today. I think what actually happened is `fn backtrace(&self) -> Option<Backtrace>` was removed shortly before 1.0, but there were some misgivings about that and some on the core team wanted to bring that back eventually. And that was the main reason that it could not move to core, because that would make it a breaking change to bring `fn backtrace` back.
So, hearing that it is now actually in core is great, that resolves uncertainty I've had for like 7 years. Thank you!
Yes. I'm only counting what has been part of the stable API since Rust 1.0.
I agree that following the stabilization path and future direction of a feature can be difficult.
> Because if the only purpose was error chains, it could have been in core on the day of rust 1.0, as it is today. I think what actually happened is `fn backtrace(&self) -> Option<Backtrace>` was removed shortly before 1.0, but there were some misgivings about that and some on the core team wanted to bring that back eventually. And that was the main reason that it could not move to core, because that would make it a breaking change to bring `fn backtrace` back. At least that's what I remember from PRs I followed at the time. (There may have been other reasons besides this though?)
The only purpose is not chaining. Backtraces were definitely a desirable part of it! But we wanted to move `Error` to `core` and having backtraces be tightly coupled to `Error` would not allow that. As far as I remember, that was always the tension. To my memory, it wasn't until Jane wrote down the "generic member access" direction[1] for the `Error` trait that this tension was finally broken.
[1]: https://github.com/rust-lang/rfcs/pull/2895
I love working in rust. I love Result, and the ? sigil, etc. I love the enums and match, and how non_exhaustive works. I love all that.
I think that means I love rust error handling as well!
I just didn't love `std::error::Error`, it caused some pain. I think they should have just waited to stabilize until it was ready to go in core. If it wasn't there on day 1, rust error handling would have worked great! It's actually a pretty small and inessential part of the rust error handling story. I mean at this point I've hardly used it at all in 8 years using rust almost every day.
And all those churning crates, failure etc., like, that was just some people's opinions about what a fancier error framework might look like. And absolutely you're right we didn't need to get on that treadmill.
I wanted to support the OP's minimalist take though and complement it with my own though -- for a certain type of engineer that I have worked with, "use std::error::Error` looks like a "best practice" and that means that we aren't writing "good" or "idiomatic" rust if we don't use it. I do think it's a completely valid choice to eschew it.
Thank you -- I just wanna say, I read a lot of your writing and I love your work. I'm not sure if I read that blog post so many years ago but it looks like a good overview that has aged well.
> I happily downcast in ripgrep: https://github.com/BurntSushi/ripgrep/blob/0a88cccd5188074de... > > That also shows the utility of an error chain.
Yeah, I mean, that looks pretty nice.
I still think the error chain abstraction should actually be a tree.
And I think they should never have stabilized an `std::error::Error` trait that was not in core. I think that itself was a mistake. And 8 years later we're only now maybe able to get there.
I actually said something on a github issue about this before rust 1.0 stabilization, and that it would cause an ecosystem split with embedded, and that this really should be fixed, but my comment was not well received, and obviously didn't have much impact. I'll see if I can find it, it's on github and I remember withoutboats responded to me.
Realistically the core team was under a lot of pressure to ship 1.0 and rust has been pretty successful -- I'm still using it for example, and a lot of embedded folks. But I do think I was right that it caused an ecosystem split with embedded and could have been avoided.
But also, to be clear, `core::error::Error` has been a thing for over a year now.
> And the benefit of shipping a janky version of `std::error::Error` however many years ago, almost all of which got deprecated, seems hard to put a finger on.
Again, I think you are overstating things here. Two methods were deprecated. One was `Error::description`. The other was `Error::cause`. The latter has a replacement, `Error::source`, which does the same thing. And `Error::description` was mostly duplicative with the `Display` requirement. So in terms of _functionality_, nothing was lost.
Shipping in the context of "you'll never be able to make a breaking change" is very difficult. The downside with embedded use cases was known at the time, but the problems with `Error::description` and `Error::cause` were not (as far as I remember). The former was something we did because we knew it could be resolved eventually. But the APIs that are now deprecated were just mistakes. Which happens in API design. At some point, you've got to overcome the fear of getting it wrong and ship something.
Also Editions mean we can go back and fix things "for the future" in some cases if that's worth the price. For example I believe it's worth the price for the built-in ranges to become IntoIterator, indeed I hoped that could happen in the 2024 edition. It was, I think we'd both agree, worth it to fix arrays in 2021.
This is one of the less celebrated but more important wins of Rust IMO because it not only unlocked relatively minor direct benefits it licensed Rust's programmers to demand improvements, knowing that small things were possible they wanted more.
I don't have a super good understanding of how it actually works in rustc. In C++ typically they would use name mangling tricks to try to keep new and old standard library symbols from clashing if they decided to break compatibility. Probably with rustc they are happier to leave it unspecified. I have a hard time building a mental model of what kinds of things would be totally impractical to change with an edition.
1. The new ranges are stabilized, they become usable from stable Rust, as well as a core::ops::Range, the half-open range type in today's Rust, you'd be able to make a core::range::Range, a modern rendition of the same idea. The new type implements Copy and IntoInterator, and provides an iter() method to iterate over immutable references, all things the old type wasn't designed to accommodate.
2. A new Rust edition says that now A..B is syntax sugar for core::range::Range { start: A, end: B } rather than the same idea but for core::ops::Range. In your existing Rust your range notations are thus still core::ops::Range, but in new Rust projects written for a new edition they are core::range::Range with the nicer semantics.
So, there's no name mangling trick, the types continue to both exist, just when you write 0..4 meaning the integers 0, 1, 2 and 3, now you mean the modern type. You may need to write a little glue for older libraries to turn your modern types into Iterators they expect, but it's trivial and soon enough all modern Rust code would behave as though the ranges had always implemented Copy and IntoIterator just as today Rust code behaves as though the arrays had nice properties which in say 2018 Edition they did not.
In terms of mental models, an Edition can change what the concrete syntax of Rust means, so often the strategy is in two parts, 1: In ordinary releases ship an awkward way to express the new idea, this is 100% compatible with existing code, but has poor ergonomics - then 2: In an Edition change the language so that the new idea is easy to express
So you can see for Ranges, that first element would be stabilizing core::range::Range (and other similar types) then the edition would change the syntax.
I think it's true that right up until 1.0, backtrace was a part of `trait Error`, then it was deprecated and removed, but there were ongoing discussions as late as 2021 about how the "point" of `std::backtrace::Backtrace` was to attach it to an `Error`. I have lots of links and references there.
As a user, that kind of meant that as long as I thought `trait Error` might grow backtraces someday, I should stay away from it in order to be friendly to embedded. And as long as it wasn't in core, that could still happen. I hope you can agree that it was far from clear what was going to happen until relatively recently.
Go dosent deviate from the norm. It’s the same style we’ve had back from the billion dollar mistake. Not saying it’s wrong rust’s is just different. Tradeoffs and such.
if err != nil {
`anyhow` has exactly one optional dependency (backtrace). `thiserror` has three (proc-macro2, quote, syn) which are at the base of practically the entire Rust ecosystem.
Unless the author has zero dependencies in general, I'll bet they have all of the above dependencies already.
¯\_(°ペ)_/¯
I've started also dropping `thiserror` when building libraries, as I don't want upstream users of my libraries to incur this additional dependency, but it's a pain.
The general argument against adding something to `std` is that once the API is stabilized, it's stabilized forever (or at least for an edition, but practically I don't think many APIs have been changed or broken across editions in std).
The aversion to dependencies is just something you have to get over in Rust imo. std is purposefully kept small and that's a good thing (although it's still bigger and better than C++, which is the chief language to compare against).
Rust has a too-small stdlib because they want to avoid a calcified stdlib like C++ and Python, which both have too-big stdlibs.
This is a law of nature, your stdlib can either be too small or too big. It cannot be the right size. At least it isn't C.
Unless you're working on something with extremely limited scope, dependencies will become unavoidable; without resorting to reinventing many wheels.
> This is not THE idiomatic way to write rust but rather the way that I write errors. > impl From<std::num::ParseIntError> for DemoError { > fn from(error: std::num::ParseIntError) -> Self { > DemoError::ParseErr(error) > } > }
This introduces a lot of observability risk.
You've essentially built a context eraser. By using a generic From impl with the ? operator, you’re prioritizing brevity during the "happy path" write, but you're losing the "Why" of the error. If my_function has five different string-to-int conversions, your logs will just tell you "Invalid Digit." Good luck grep-ing that in a 100k LOC codebase.
map_err can help fix this, but look at what that does to your logic:
In a real-world refactor, someone is going to change first_input to validated_input and forget to update the variable inside that closure. Now your error message will report the wrong data. It sends the SRE team down a rabbit hole investigating the wrong input while the real bug sits elsewhere.And by calling error.to_string() in your Display impl:
...you are manually "flattening" the error. You’ve just nuked the original error's type identity. If a caller up the stack wanted to programmatically handle a specific ParseIntError variant, they can't. You've turned a structured error into a "stringly-typed" nightmare.Realistically your risk of mismanaging your boilerplate is significantly higher than a supply chain attack on a crate maintained by the core library team.
I find it hard to believe. Since a huge class of errors are caught by compile time static analysis, you don't really need an exception system, and errors are basically just return values that you check.
It's much more productive just to use return values and check them, wrap return values in an optional, do whatever. Just move on, do not recreate the features of your previous language on a new language.