It’s a nice summary about error handling in Rust, but doesn’t present anything new for anyone who has been writing Rust for a while.
The title “more than you’ve ever wanted to know about errors in Rust” had me expecting to learn something new.
Still a nice intro though, and could for example form part of the documentation for shuttle in the future, to help people who are new to Rust get into using shuttle.
Yea this should be called "The minimum you need to know about errors in Rust". These click bait self promotion blog post titles are getting out of hand.
I agree. This would’ve likely saved me a lot of time that was spent on trial and error but now it’s not new. But this is great reference to forward to new folks getting started.
I would have liked a small conclusion under the chapter that names thiserror, anyhow and eyre, arguing why you might pick one library over another, and which one to pick if you don't have any requirements (anyhow). This blog post compares the two first with examples on when they're neat and cumbersome:
When I first used Rust, I was using failure and it just made sense to me. And then we got the 2nd (3rd?) wave of error libraries and nothing has ever made simple sense to me since.
Never saw this link and it’s wonderful, it’s exactly how I think of errors. Thanks!
Yes, that's what the docs linked say. And the GP is correct in saying "you definitely can." The GP is a correction to the article which incorrectly states that "there is no way to incept [sic] a panic in the current thread." The correct phrasing would be "there is no way to guarantee that you can recover from a panic in the current thread."
In the context of the article, this is a somewhat minor point. Because you can't guarantee the recovery from panics, you often code as if you can never recover from panics. And thus, when you write code that can panic (for example, `slice[i]`), it's best to conservatively assume that any such panic won't be recovered from. Still though, the existence of a recovery mechanism can be useful advice in some circumstances: https://docs.rs/regex/1.*/regex/#panics
The broader story here is a knot that is difficult to untangle because 1) people have different values and 2) words mean different things to different people. I tried to untangle some part of it here (although it's more broad than just "panics can or can't be recovered from," it's still all connected): https://blog.burntsushi.net/unwrap/
To extend on this: application developers can actually guarantee it, because they control the compilation process, which is what decides if it aborts or not. Libraries have less control / no guarantee.
> To extend on this: application developers can actually guarantee it, because they control the compilation process, which is what decides if it aborts or not.
They can control that panic isn't compiled as abort, but even then, if there's a destructor that can panic, then if it panics during unwinding, it aborts. So even the app developer cannot guarantee to catch all panics.
I was very happy to see a recap of all the basics. Sometimes, with new crates coming and going, I worry/imagine I may be falling behind on the latest "rustic" ways to handle certain things like this.
If you're writing library code and you are new to rust, just stick with `thiserror` and nest your error types (ie: have lower-level errors wrapped up in higher-level errors using #[from]). It's barely more effort than using the Any* error types and the benefits of having concrete errors greatly outweigh the tiny bit of extra effort.
This is a general principal in the Rust standard library, but it is confusing at first. Similarly you can construct a HashMap without restriction but calling `.get` on it requires the key to implement Hash.
I'll let someone else clarify the reasoning because I'm not confident I remember it correctly. I though this was in the Rust API guidelines but I can't find it after a quick skin.
I couldn't find it in the API guidelines either. From what I understand, the idea is that any trait bounds, which includes generic type parameter bounds and lifetime bound on a type (struct or enum) would be repeated back in the impl block
I find the concept of unrecoverable panics extremely weird. Crashing your entire program just because something unexpected happened? Erlang would lime a word with you :)
This is not fair summary of the situation. Panics can be “caught”, most Rust web framework will catch panic when handling a http request and return 500, leaving other requests unaffected (more or less). This is almost no different from Python, Java, even Erlang.
Panic is a way for Rust library ecosystem to signal this error in un-recoverable, and is probably a bug in program, for which you would be happy getting Sentry or some such reporting.
Most errors in Rust are handled using mechanisms described in post using Result. Some, this is probably a bug in code, mechanism should be present in other languages.
Well not every program has to be a super robust daemon recovering from any kind of problem.
There's a good bunch of programs that are just supposed to read some inputs, perform a task and crash with an error message and exit code if anything goes wrong.
FWIW, the article incorrectly but explicitly and repeatedly states that panics are not recoverable, and so this is a gripe with the content causing misinformation.
It’s not really a misconception exactly. They are to be used for unrecoverable errors, conceptually. Because of this, the ecosystem tends to treat them as such, even if you can choose to require the ability to catch them, just as if you can also require the ability not to catch them, which is one of the reasons why the ecosystem follows the rule: they can’t rely on the behavior (unless they want to limit their audience and that’s basically never worth it).
You cannot catch a panic, only an unwind. And you shouldn’t assume that your panic will unwind as a downstream user of your crate can just as easily change panics to aborts
Anyhow and Eyre seem cool, but completely losing all information about what types of Errors the function returns in favor if what is essentially a trait object of std::error::Error seems... questionable to me. I value type safety a lot.
I would say the same thing, but really it depends. Sometimes it really doesn’t matter what type of error it was, since all errors lead to the same outcome (print a simple error message and exit). Sometimes you just don’t know what kind of errors you’ll be dealing with, usually because it’s early in the design of the program and you haven’t explored the design space yet. In cases like these, crates like anyhow make perfect sense. It lets you put off making any complicated decisions about error types until you need to and are ready to.
It does feel like checked exceptions. But the subtle but important thing is that results are not control flow constructs. They're just values with syntax sugar, so all other language features work with them (you can store, modify, nest, pass them without try/catch/throw constructs)
It's not subtle in that it's not opaque to the programmer that it's just a "regular" sum type
It's not important in that it doesn't change what the programmer can do with it, it just changes the syntax used.
Return an error: return Err(...) vs. throw ...
Propagate error to caller: ? vs. nothing
Store/modify/nest/pass: Without try/catch/throw construct vs. WITH try/catch/throw construct.
The much more important difference is between bounded vs. unbounded error types. Rust falls in the former camp and inherits all the problems (and benefits) that come with it.
I'm not sure what you mean by bounded. There's `dyn Error` that lets you unify all error types. You can also make functions generic over the error type.
Places where people use precise concrete error types are not forced by the language, it's just the best practice for libraries.
The title “more than you’ve ever wanted to know about errors in Rust” had me expecting to learn something new.
Still a nice intro though, and could for example form part of the documentation for shuttle in the future, to help people who are new to Rust get into using shuttle.