logoalt Hacker News

osiris88yesterday at 8:54 PM1 replyview on HN

OP is spot on, no deps is the way.

I've been using rust for 8+ years, I remember the experiments around `failure` crate, a precursor to anyhow if I remember right... and then eyre, and then thiserror...

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-starter 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 anything 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. So I rapidly learned that these kinds of crates can cause much bigger problems than they actually solve.

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, and doesn’t involve std dependencies. I don't bother with implementing `std::error::Error`, because it's pointless. Display is the only thing errors need to implement, for me.

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.


Replies

burntsushiyesterday at 9:09 PM

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

show 2 replies