top | item 46414535

(no title)

burntsushi | 2 months ago

> 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.

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.

discuss

order

osiris88|2 months ago

> 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.)

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

To clarify, I'm on libs-api. I've been on it since the beginning. Stabilizing `std::error::Error` was absolutely the right thing to do. There were oodles of things in Rust 1.0 that weren't stable yet that embedded use cases really wanted. There are still problems here (like I/O traits only being available in `std`). The zeitgeist of the time---and one that I'm glad we had---was to ship a stable foundation on which others could build, even if there were problems.

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

By error accumulation, I mean a tree of errors, not a simple chain. The chain is only useful at the very lowest level.

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

I don't see any reason for something like `rootcause` to become foundational. Most errors are a linear chain and that's good enough for most use cases.

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.