10 ms·
A guide to error handling in Rust
- anacrolix 4y agotry blocks? is this a bit out of date?
- Spiritus 4y agoI think you're mixing up try blocks with the old try! macro.
- nrc 4y agoNope, try blocks are a new (unstable) feature
- tialaramex 4y agoHow so? As it says this is not yet a stable feature, but if you run nightly Rust it's available. Try blocks let you do what ? (the Try operator) does within a block, rather than needing to split out a separate function for it, which makes sense because why should functions be special in this way?
- anacrolix 4y agoI have indeed confused it with try! macro
- sposeray 4y ago[dead]
- tialaramex 4y agoI feel like this document makes the Try operator (?) and its associated trait more mysterious than necessary. Most people probably won't need to implement Try, especially before it is stabilised, but it's not that much more complicated than say, AddAssign the trait which you implement to make the Add Assignment (+=) operator work on your type. The key trick of Try is that it converts something (by default an Option or a Result or async Polls of those types) into a ControlFlow†. This is the one nice trick about Exceptions in languages which have them - they influence control flow, but Rust reified it as a vocabulary type which I think is much better. We can pass this thing back to somebody who cares about the resulting control flow, not just suddenly wrench the control flow out from under the rest of the software. † unlike Try, ControlFlow is actually a stable type you can use today in your Rust and, like std::cmp::Ordering it's useful even just as a vocabulary type, disregarding its semantics. Library A and Library B, written by different people, in different circumstances, both agree that ControlFlow::Continue is continue and ControlFlow::Break is break whereas who knows what the boolean false from Library A means to Library B, let alone what if anything Library B's custom type BPartialResult means to Library A's code.
- mmastrac 4y agoTIL what ControlFlow is. This is super interesting and solves some problems I thought were impossible. Error handling has gone from uber painful in 2018 to pretty decent in the latest editions.
- petertodd 4y agoBTW if you have read about the `Try` trait before and are wondering what `ControlFlow` is, read it again: https://doc.rust-lang.org/nightly/std/ops/trait.Try.html https://doc.rust-lang.org/nightly/std/ops/trait.Try.html `Try` was recently changed significantly with the introduction of `ControlFlow`. IMO it's a big improvement.
- gwbas1c 4y agoI do some hobby projects in Rust. One gotcha that I hit was using ? in sample code in documentation. It didn't work, so I had to replace all of my ? with .unwrap(). I generally consider .unwrap() a poor example, because it encourages writing code that could crash a program unnecessarily.
- cercatrova 4y agoYou can set lints for cargo, for example to warn or even disallow compiling with any `unwraps` or `expect`s. I use cargo-cranky which makes using lints super easy, cargo doesn't yet have native functionality to set which lints should be enabled or disabled.
- erk__ 4y agoYou can use it in sample code in the documentation, but you will need to add a bit of boilerplate around it: https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#using--in-doc-tests https://doc.rust-lang.org/rustdoc/write-documentation/docume...
- valenterry 4y agoToo bad Rust doesn't have union types (aka adhoc / anonymous unions) yet. Without them, using typed errors is very clumsy. Optimally, you would write the following code: fn foo(r1: Result<i32, Error1>, r: Result<i32, Error2>) { let i1 = r1?; let i2 = r2?; // ... } and Rust would infer the return type to be Result<String, Error1 | Error2> without having to do any extra definitions or conversions.
- insanitybit 4y agoAnonymous sum types are something I want for error handling as well. In practice though I'm not sure it would really make my life that much better.
- masklinn 4y agoIt would make life a bit terser, but I’d rather have polymorphic variants. And maybe only anonymous enums over polymorphic variants. That would make precise error handling on libraries quite a bit better.
- valenterry 4y agoNot sum types. Those are union types. The difference is important, since if you work with two results (or two functions that return results) that use the same error-type you most often don't want to end up with a tuple of two times the same error but simply A<String, Error>. Of course, if you care about which error is from which function, you can always easily do that by wrapping them into a sumtype, but in practice this is a rather rare use-case in application code at least.
- insanitybit 4y agoUnions don't have a discriminant. Anonymous Sum types have a discriminant, you just can't name it. Unions in Rust are unsafe because you can't tell what the underlying value will be.
- 4y ago
- oxff 4y agoIt is perhaps too verbose by default, as indicated by popularity of thiserror and anyhow crates.
- nrc 4y agoMany of the features of such crates are making their way to the standard library, so things will definitely improve. Figuring out what is best has taken some time and Rust has not wanted to prematurely commit.
- oxff 4y agoI like what the error handling achieves, it is actually readable way to understand the divergent control flow paths (and probably majority of code is read more than written), but I do not enjoy writing the initial boilerplate, so that's good to hear.
- kibwen 4y agoIf anyone's interested in helping to shape the future of Rust's built-in error-handling story, there's an error handling project group that's been doing great work recently, e.g. the major effort to move the Error trait into libcore ( https://github.com/rust-lang/project-error-handling/issues/3 https://github.com/rust-lang/project-error-handling/issues/3 ) and stabilizing std::backtrace. You can follow along or get involved via the #project-error-handling channel on the Rust zulip: https://rust-lang.zulipchat.com/ https://rust-lang.zulipchat.com/
- exDM69 4y agoPerhaps, but it's also much better than it was two years ago and there is work going on to make it better two years in the future. A myriad of experimental prototypes (like the failure crate and its descendants) have been made, experimented with and then retired and looks like the progress is converging to these two complementary error handling crates (anyhow, thiserror, and a few mostly-compatible variants like eyre), and work going on to standardize some aspects of it so (parts of) these crates can be retired. There's also core::error that's bringing this to no-std environments. So yeah, it definitely was not great on day 1 and there's been a lot of churn on error handling but it is going in the right direction.
- svnpenn 4y agoThis glaring omission from this is the "enum idiom": https://doc.rust-lang.org/std/convert/trait.From.html#examples https://doc.rust-lang.org/std/convert/trait.From.html#exampl... they talk about it here: https://nrc.github.io/error-docs/error-design/error-type-design.html https://nrc.github.io/error-docs/error-design/error-type-des... but including more than a snippet would go a long way to that "aha" moment I think. This was frustrating for me browsing this site. The author wrote 10 pages of docs, but nearly all the examples are like 5 line snippets of code. I think examples are equally important as the discussion itself. Rust itself suffers from the same problem: https://github.com/rust-lang/book/issues/3348 https://github.com/rust-lang/book/issues/3348
- epage 4y agoWhats the "aha" moment for it, the Froms? The author might not have included that as they call out you likely shouldn't directly wrap another error. I go a step further and think that public errors shouldn't have From's for concrete types, exposing your implementation details, and that enum errors are more generally too tied to implementation details to be used in libraries.
- svnpenn 4y agoOK, but what do you do then? Its not really helpful to say "this bad", if you don't offer a "this good". Of the maybe 10 approaches I have seen to Rust error handling (including using external crates, gross), the "enum idiom" is the most elegant and flexible to me, and coming from another language feels the most natural.
- shepmaster 4y ago> public errors shouldn't have From's for concrete types > enum errors are more generally too tied to implementation details to be used in libraries I generally agree. SNAFU addresses these problems in two ways: 1. The `From` implementation is not created for the underlying error but for an intermediate type (by default). That type is private to the crate (by default) and cannot expose implementation details. 2. There's an opaque error facility to completely hide the enum details. Put together, that looks something like... use snafu::prelude::*; use std::{ fs, path::{Path, PathBuf}, }; #[derive(Debug, Snafu)] enum ErrorImpl { #[snafu(display("Could not read the config file {}", path.display()))] UnableToReadConfig { source: std::io::Error, path: PathBuf, }, #[snafu(display("Could not write the config file {}", path.display()))] UnableToWriteConfig { source: std::io::Error, path: PathBuf, }, } #[derive(Debug, Snafu)] pub struct Error(ErrorImpl); pub type Result<T, E = Error> = std::result::Result<T, E>; pub fn do_stuff_with_config(path: &Path) -> Result<()> { let config = fs::read_to_string(path).context(UnableToReadConfigSnafu { path })?; fs::write(path, config).context(UnableToWriteConfigSnafu { path })?; Ok(()) } Other things about SNAFU: - It's very easy to add valuable context to the errors. See how the `&Path` context is transformed to a `PathBuf` with low ceremony in the example. - You can create struct- or enum-based errors. - You can use "stringly-typed" errors (akin to anyhow) but in combination with strongly-typed errors. This allows you to start out with a loose error handling regimen and make it stronger as you go along. - There's support for capturing backtraces or lightweight file/line/column information. - There's a pretty error reporter for usage with `main` functions or tests. - There's support for the nightly-only Provider API.
- MontagFTB 4y agoAn aside: what’s the template that gets articles formatted this way in GitHub pages? I found it very appealing.