(no title)
nirui | 1 month ago
I don't think you understood my point. Instead, I believe you launched your argument too quickly. Because if you continue reading, you'll see:
> ... but if you want to so the same in Go, there will be a nightmarish manual type discovery and tracking operation waiting for you.
Which should deter you from providing the example where you declared the `ErrFoo` and `ErrBar` type, since it's basically the "nightmarish manual type discovery" scenario unfolding in basically realtime.
But let's continue with the example to make my argument more full:
Let's say one day the `ErrFoo` and `ErrBar` type is not sufficient, so you declares another type `Err2000`. Now what happens down stream?
Since the some function may just return an error interface, there's no way for the end user to know you've added that error type. From their prospective, everything is unchanged, and still compiles just fine.
If the user's code is relying on the exact error type for branching, then they have to perform "manual type discovery and tracking" to monitor the changes to your code (and everything above it, really) to ensure that the branching is still done correctly.
In Rust however, since error is a emun, all error conditions must be handled or explicitly ignored during a `match`, if you write something like
match File::open("filename.txt") {
Ok(file) => {}
Err(io_err) => match io_err.kind() {
ErrorKind::IsADirectory => {} // Notice how not every error type is handled
},
}
The compiler will just stand up and make your ass weep. Meaning if you add some new error conditions, your user will know that for sure when they compile.(BTW, `std::io::ErrorKind` is indeed `non_exhaustive`, but you the maker of the error type still have to explicitly/intentionally declare it to make it non_exhaustive)
So, let's read my comment again:
> Rust's error handling encourages such branched out handlings by default, but if you want to so the same in Go, there will be a nightmarish manual type discovery and tracking operation waiting for you.
(Also, "by default" is there to indicate that yeah you can boxing a `std::error:Error` trait in Rust, then you ended up with something like how Go handles error. I know.)
iamcalledrob|1 month ago
Go maintains a strict backwards-compatibility guarantee (*love* this), and using sum types for errors would mean, at least in the stdlib, either (a) no new errors can be introduced, or (b) new versions of Go would break builds when new errors are introduced.
Personally, I value my code compiling in the future over more explicit error handling. New errors will hit my catch-all branch, and I can special-case them later as I see fit. But it's a philosophy/values thing.As an aside, I very rarely find myself actually wanting a sum type for errors. I usually want to check for a few specific errors, but I almost always want a catch-all for truly unexpected stuff. Sum typed errors in any complex system often end up needing a `.other()`-style case anyway because in the real world so much can go wrong.
Rust is by no means perfect either. The `?` operator steers you in the direction of ignoring errors rather than thinking about them, which I think leads to worse outcomes (i.e. that Cloudflare outage)
nirui|1 month ago
> Personally, I value my code compiling in the future over more explicit error handling. New errors will hit my catch-all branch, and I can special-case them later as I see fit. But it's a philosophy/values thing.
OR, you can just do `fn() MyError` then `if myError := fn(); !myError.OK()` opposite to just `fn() error`. I actually use this "trick" on many of my projects and they worked great.
I'm not really sure why Go defenders MUST praise Go's error handling as if it's flawless and thus all fault signals must be a `error`. As I pointed out few comments back, Go is clearly promoting binary error handling i.e. `if err != nil { return error }`, as doing branched out handling for specific error type is much harder (one example is the "nightmarish manual type discovery" mentioned above), so it's far from flawless. It's useful, sure, and people are using it, but it's not flawless.
BTW:
> I can special-case them later as I see fit
See? You already doing manual type discovery, they slipped the idea so naturally into your brain you don't even notice the struggle. But what if the number of types that you need to take care of keeps growing? Is it scalable?
You know what, maybe they should add Sum type for error handling. Hope it's not too late now since many people already camped on the idea that returning an interface then and casting it for error handling is the best idea ever.
iamcalledrob|1 month ago