(no title)
burntsushi | 2 months ago
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.
osiris88|2 months ago
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. 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.
burntsushi|2 months ago
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.
osiris88|2 months ago
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.
burntsushi|2 months ago
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.