I think the problem is Rust does a great job at providing the basic mechanics of errors, but then stops a bit short.
First, I didn't realize until relatively recently that any `String` can be coerced easily into a `Box<dyn Error + Send + Sync>` (which should have a type alias in stdlib lol) using `?`, so if all you need is strings for your users, it is pretty simple to adorn or replace any error with a string before returning.
Second, Rust's incomplete error handling is why I made my crate, `uni_error`, so you can essentially take any Result/Error/Option and just add string context and be done with it. I believe `anyhow` can mostly do the same.
I do sorta like Go's error wrapping, but I think with either anyhow or my crate you are quickly back in a better situation as you gain compile time parameter checking in your error messages.
I agree Rust has over complicated error handling and I don't think `thiserror` and `anyhow` with their libraries vs applications distinction makes a lot of sense. I find my programs (typically API servers) need the the equivalent of `anyhow` + `thiserror` (hence why I wrote `uni_error` - still new and experimental, and evolving).
An example of error handling with `uni_error`:
use uni_error::*;
fn do_something() -> SimpleResult<Vec<u8>> {
std::fs::read("/tmp/nonexist")
.context("Oops... I wanted this to work!")
}
fn main() {
println!("{}", do_something().unwrap_err());
}
Ref: https://crates.io/crates/uni_errorWhich is why it's weird to me that the error handling culture of Rust seems to steer so directly towards where Go tries to get to!
I have a love/hate relationship with Go. I like that it lets me code ideas very fast, but my resulting product just feels brittle. In Rust I feel like my code is rock solid (with the exception of logic, which needs as much testing as any other lang) often without even testing, just by the comfort I get from lack of nil, pattern matching, etc.
The joke I like to snark about in these kinds of comparisons is that I actually like computer science, and I like to be able to lay out a tree structure when it makes sense to do so, without consulting a very large book premised on how hard it is to write a doubly-linked list in Rust. The fun thing is landing that snark and seeing people respond "well, you shouldn't be freelancing your own mutable tree structures, it should be hard to work with trees", from people who apparently have no conception of a tree walk other than as a keyed lookup table implementation.
But, like, there are compensating niceties to writing things like compilers in Rust! Enums and match are really nice there too. Not so nice that I'd give up automated memory management to get them. But nice!
I'm an ex-C++/C programmer (I dropped out of C++ around the time Alexandrescu style was coming into vogue), if my background helps any.
It doesn't? In Go, I allocate (new/make or implicit), never free. In Rust, I allocate (Box/Arc/Rc/String), never free. I'm not sure I see the difference.
> Sometimes being able to make those decisions is useful, but usually it is not.
Rust makes you think about ownership. I generally like the "feeling" this gives me, but I will agree it is often not necessary and "just works" in GC langs.
> I actually like computer science, and I like to be able to lay out a tree structure when it makes sense to do so, without consulting a very large book premised on how hard it is to write a doubly-linked list in Rust. The fun thing is landing that snark and seeing people respond "well, you shouldn't be freelancing your own mutable tree structures, it should be hard to work with trees", from people who apparently have no conception of a tree walk other than as a keyed lookup table implementation.
I LOVE computer science. I do trees quite often, and they aren't difficult to do in Rust, even doubly linked, but you just have to use indirection. I don't get why everyone thinks they need to do them with pointers, you don't.
enum Node {
Leaf,
Branch {child: Box<Node>, parent: Option<Box<Node>> },
}
Compared to something like Java/C# or anything with a bump allocator this would actually be slower, as Rust uses malloc/free, but Go suffers from the same achilles heel here (see any tree benchmark). In Rust, I might reach for Bumpalo to build the tree in a single allocation (an arena crate), but only if I needed that last ounce of speed.
Again: I think Rust as a language gets this right, better than Go does, but if I had to rank, it'd be (1) Rust explicit enum/match style, (2) Go's explicit noisy returns, (3) Rust terse error propagation style.
Basically, I think Rust idiom has been somewhat victimized by a culture of error golfing (and its attendant error handling crates).