7 ms·
Having had to work with various application written in rust... i find they have some of the most terrible of errors. "<some low level operation> failed" with ab
by arccy 1y ago
Having had to work with various application written in rust... i find they have some of the most terrible of errors. "<some low level operation> failed" with absolutely no context on why the operation was invoked in the first place, or with what arguments.
This is arguably worse than crashing with a stack trace (at least i can see a call path) or go's typical chain of human annotated error chains.
- nixpulvis 1y agoI don't think this is really a failing of the language, just a bad practice in assuming that you can bubble up a low level error without adding some more context to why it was trigged in this case. Short of matching the error and returning a new error with more info, I'm not sure what the idiomatic solution is personally. But I also haven't spent as much time as I should with error crates. I agree it's an issue, and I hope the community centers around better practices.
- arccy 1y agoI think it is a language issue, if the language makes a bare return much easier than the alternative, then that's what people will be inclined to do.
- nixpulvis 1y agoWhen I say it's not a language issue, that's because I can imagine a crate or design pattern filling this role, and I cannot as easily imagine a change to the language itself that would make it better. But I also haven't given this as much thought as it deserves.
- arccy 1y agogetting rid of ? might help...
- nixpulvis 1y agoI don't think that helps, there are a lot of intermediate steps that rightfully propagate or union errors and `?` makes that a lot easier. The issue is figuring out when to not do that and wrap a low level error with a higher level error without losing the critical information and making things to generic.
- RealityVoid 1y agoEasiest error handling is just .unwrap(). It's true you can just leave it messy like that, but it at least leaves you an obvious trail where you can go and clean up the error reaction.
- nixpulvis 1y agoEasier yes; better absolutely not. A user should not be given an unwrap error and expected to read the code to figure out what went wrong. In general, if you're making serious software, you shouldn't assume source knowledge at all in user facing messaging.
- MangoToupe 1y agoThis seems incredibly bad faith—using "unwrap" is closer to using an assert than it is at being "user facing messaging". Asserts don't typically come with manuals explaining what the user should do to fix the software. We all have moments when we're developing software and haven't fully developed the domain. Not requiring exhaustive error handling during this process is a massive help. Obviously, fully enumerated errors are good, and catering diagnostics to the users is even better. But there's more to software than shipping perfect code.
- nixpulvis 1y agoI do not understand your point. Are you saying more code should use asserts for catching error cases? Nobody's saying perfection is 100% required, but there are ideals and we should aim for them and design with principle. One of those principals should be quality error messages. Hell, the rust compiler itself is a great case study for how improving error messages for users massively helps with adoption and quality of life. It's pretty rare that you hit an internal compiler error (ICE) too which is nice.
- MangoToupe 1y agoI'm certainly not against high-quality diagnostics, I'm just saying that few people expect an unwrap to be sufficient for this, and it's odd to me to look at an unwrap and see it as error messaging (outside of what an assert is supposed to provide—eg a clear inconsistency in logic a developer needs to interpret). But to figure out which errors you need to appropriately parameterize and pass up the stack, you first need to understand what you're building. Without unwrap, we would basically have to engage in exhaustive enumeration as we develop. Which is fine, if you're willing to put that effort in! But that's part of why things like Haskell can struggle to win people over—for better or worse, half-broken code (semantically, in domain terms) that compiles and runs is a powerful tool mid-feature. But yes, this does require that people care about diagnostics to begin with. I don't think that's worth forcing; most of us develop this instinct through frustration at dealing with the fallout of someone not caring. (And FWIW I find the combination of explicitly-structured Results that are manually passed up the stack with RAII to be a very, very sweet spot for development velocity, and also easy to teach. I very much prefer this to both exceptions and go's approach.)
- deleted 1y ago[deleted]
- ninkendo 1y agoYeah, sometimes I think the `?` early-return operator is an anti-pattern. It’s too easy to reach for, and results in these sort of context-less “Operation failed” messages. I wish there were a better way to enforce that you can’t use the early return syntax without adding context to the error in some structured way (so that the result is something resembling a stack trace.) Maybe a particular WrapsError trait that the compiler knows about, which constructs your error with the wrapped error and callsite information, so that you can annotate it properly, and is automatically used for `?` early-returns if the return type supports it. At some point it does feel like we’re just reimplementing exceptions poorly though. > or go's typical chain of human annotated error chains It’s interesting you say this, because go has basically the exact same analogous problem: People can just do `if err != nil { return nil, err }`, and it results in the same problem. And the last time I did much go programming I remember having a very similar complaint to yours about Rust. Maybe more go programmers have begun to use annotations on errors since then? There’s nothing really different about rust here, anyhow/etc make it just as easy to annotate your errors.
- arccy 1y agomaybe because lots of go enterprise software development turns on a bunch of linters, which will include wrapcheck that complains if you don't wrap https://github.com/tomarrell/wrapcheck https://github.com/tomarrell/wrapcheck
- tough 1y agogo has a really good ecosysstem between wrapcheck, gocritic, and golint
- more-nitor 1y agohmm this might be solved in some simple ways: 1. create `with_err` function: some_mayerr_func().with_err_log()? 2. add 'newbie beginner mode(default)' / 'expert mode (optimized)' for rust: - current Rust default is a bit too focused on code-optimization, too much so that it defaults to removing backtrace - default should be 'show every backtrace even for handled errors (in my code, not the lib code -- bonus points for "is handled" checkbox)'
- jeroenhd 1y agoThere's no functional difference between Go and Rust here, other than that the Rust code you seem to interact with doesn't seem to bother wrapping errors as often. Neither languages have very good errors. Then again, no language I know does errors well; I'd like Java's error system if Java developers didn't insist on making every exception a runtime exception you can't know you need to handle unless you read the source code. FWIW, you may be able to see the stack traces by enabling them in an environment variable (export RUST_BACKTRACE=1). By default, errors are dumped without context, but enabling back traces may show stack traces as well as detailed human-written context (if such crates are in use).
- tsimionescu 1y agoThere is a difference between Go and Rust on the error ha sling front. In Go, the error handling is forced to be so verbose that adding a little bit of context doesn't significantly increase code size/effort, so programmers tend to actually add that context. Also, there is a very simple function for adding context to errors in the stdlib itself, fmt.Errorf. In contrast, the difference in Rust between just returning an error (a ? at the end of the expression that can return one) vs adding context is much higher. Especially so since the standard library doesn't offer any kind of error context, so you either have to roll your own or use some error handling crate (apparently anyhow is very popular). So, while at the language level there is little difference, the ecosystem and "incentives" are quite different.
- dllthomas 1y ago> the difference in Rust between just returning an error (a ? at the end of the expression that can return one) vs adding context is much higher I don't disagree with what you've said, but I think this is likely to be read too strongly. Adding a map_err before the ? is only a big step up in verbosity because the ? alone is so very terse.
- lesuorac 1y agoI just always make my own error type and as part of their creation throw a backtrace into the struct. Then using `?` gives me a stack trace for free.
- nulld3v 1y agoI don't think it's worse than a stacktrace. If the app is bubbling up low-level errors like that, it is almost certainly a "lazy" error handling library like anyhow/eyre which allow you to get a stacktrace/backtrace if you run the app with: `RUST_BACKTRACE=full`. The ecosystem should probably enable backtraces by default though, for all GUI apps at a minimum, the overhead it adds is almost always worth it. It's not good practice to use "lazy" error handling in performance-oriented code anyways, it will generate tons of heap allocations and uses dynamic-dispatch.
- jgraettinger1 1y agoI would recommend the `anyhow` crate and use of anyhow::Context to annotate errors on the return path within applications, like: falliable_func().context("failed to frob the peanut")? Combine that with the `thiserror` crate for implementing errors within a library context. `thiserror` makes it easy to implement structured errors which embed other errors, and plays well with `anyhow`.
- kaathewise 1y agoYeah, I found `anyhow`'s `Contex` to be a great way of annotating bubbled up errors. The only problem is that using the lazy `with_context` can get somewhat unwieldy. For all the grief people give to Go's `if err != nil` Rust's method chaining can get out of hand too. One particular offender I wrote: match operator.propose(py).with_context(|| { anyhow!( "Operator {} failed while generating a proposal", operator.repr(py).unwrap() ) })? { Which is a combination of `rustfmt` giving up on long lines and also not formatting macros as well as functions
- deleted 1y ago[deleted]
- timhh 1y agoYeah I agree - in fact I ran into this very issue only hours ago. The entire error message was literally "operation not supported on this platform". Yeay. https://github.com/rust-lang/rust/issues/141854 https://github.com/rust-lang/rust/issues/141854 > I'll also note that this was quite annoying to debug since the error message in its entirety was `operation not supported on this platform`, you can't use RUST_BACKTRACE, rustfmt doesn't have extensive logging, and I don't fancy setting up WASM debugging. I resorted to tedious printf debugging. (Side rant: for all the emphasis Rust puts on compiler error messages its runtime error messages are usually quite terrible!) Even with working debugging it's hard to find the error since you can't set a breakpoint on `Err()` like you can with `throw`.