6 ms·
I find that interesting, and like the Rust way of error handling much more. Go error handling blows the code up a lot and makes the code less comprehensible.
by kitkat_new 4y ago
I find that interesting, and like the Rust way of error handling much more.
Go error handling blows the code up a lot and makes the code less comprehensible.
res1, err1 = canFail()
if err1 != nil {
return err1
}
res2, err2 = canFail()
if err2 != nil {
return err2
}
return res1 + res2, nil
becomes
res1 = canFail()?
res2 = canFail()?
return Ok(res1 + res2)
guess what is more readable
- movedx 4y agoAs someone who's not a day-to-day developer/software engineer: the Go code is more readable. The Rust is interesting, for sure, but I don't know what "?" is doing. I like the use of the question mark as it makes the act of calling the function a question - did it work or not? But what's not obvious is what happens if it did not work? What happens then? Where does the error object go? Is there something that represents the error? Hidden magic isn't good magic, in my opinion. Just be explicit. Another issue with the Rust example is discipline. I don't know Rust, but the example you've given implies I can not include "?" in the call and just ignore any errors entirely? (What happens if I do that and there's an error? This isn't explicit enough.) Go, in comparison, does the opposite: it forces you to handle the error or literally ignore it. You cannot forget to deal with an error in Go. And of course, Go gives you an error object you can work with (something I'm sure Rust does too, but it's not obvious to me as a none Rust developer.) I prefer my languages to be statically typed, statically linked, and very explicit in their syntax. It results in a bit of extra work up front for compile and run time safeties, and the ability to easily re-read the code at a later date.
- wtetzner 4y agoI’m not sure that optimizing readability for someone unfamiliar with the language is the right tradeoff. Rust code uses a Result<T, E> type for error handling: it can either be Ok(T) or Err(E), never both. The question mark operator tries to extract the value wrapped in Ok. If the Result is Err and not Ok, it returns the error. It’s not “hidden magic”, it’s a well-defined operator. > I don't know Rust, but the example you've given implies I can not include "?" in the call and just ignore any errors entirely? If you don’t unwrap the value from the result, then you still have a Result<T, E> instead of a T, and will get a type error.
- kitkat_new 4y ago> But what's not obvious is what happens if it did not work? What happens then? Where does the error object go? Is there something that represents the error? Hidden magic isn't good magic, in my opinion. Just be explicit. the question mark operator simply checks if there was an error, and if there was one, it returns it. Practically, it does the same as the GO Code - and the only reason you don't know is, because you don't know Rust. > I don't know Rust, but the example you've given implies I can not include "?" in the call and just ignore any errors entirely? no, omitting the ? will give you a compiler error. You can ignore the error case by writing: res1 = canFail().expect("xz didn't work"); or res1 = canFail().unwrap(); If there's an error, the program will crash. There are other alternatives, which e.g. allow you to set 'res1' to a default value in the error case. > Go, in comparison, does the opposite: it forces you to handle the error or literally ignore it. You cannot forget to deal with an error in Go. As you see, there is no difference to Go... in this regard.
- avgcorrection 4y ago> Another issue with the Rust example is discipline. I don't know Rust, but the example you've given implies I can not include "?" in the call and just ignore any errors entirely? The issue is that you don’t know what you’re talking about.
- josephcsible 4y ago> As someone who's not a day-to-day developer/software engineer: the Go code is more readable. COBOL is what you get when you optimize for readability by people who don't know how to program. > The Rust is interesting, for sure, but I don't know what "?" is doing. I like the use of the question mark as it makes the act of calling the function a question - did it work or not? But what's not obvious is what happens if it did not work? What happens then? Where does the error object go? Is there something that represents the error? Hidden magic isn't good magic, in my opinion. Just be explicit. It gets returned to the next level up, exactly like in the analogous Go code above. And that's how it always works, so you don't have to try to figure it out separately for each use of it you see. > Another issue with the Rust example is discipline. I don't know Rust, but the example you've given implies I can not include "?" in the call and just ignore any errors entirely? (What happens if I do that and there's an error? This isn't explicit enough.) Go, in comparison, does the opposite: it forces you to handle the error or literally ignore it. You cannot forget to deal with an error in Go. You actually can't do that and will get a compiler error if you try. Rust's type system distinguishes between "a number" and "either a number or an error". On the other hand, Go will let you forget to handle an error, if you do "res1, err1 = canFail()" but then forget to return an error yourself in the "err1" case. Rust's sum types prevent that mistake entirely. > And of course, Go gives you an error object you can work with (something I'm sure Rust does too, but it's not obvious to me as a none Rust developer.) Indeed it does. I consider it a good thing that you don't have to deal with language features you're not currently using, though. > I prefer my languages to be statically typed, statically linked, and very explicit in their syntax. It results in a bit of extra work up front for compile and run time safeties, and the ability to easily re-read the code at a later date. But Rust fits the bill for all of those things, and if you care about compile time safety, it does so way better than Go.
- randomdata 4y ago> It gets returned to the next level up, exactly like in the analogous Go code above. That sounds utterly horrible. You would never actually write that Go code in the real world. Let's modify the original example slightly: res1, err1 = canFailA() if err1 != nil { return err1 } res2, err2 = canFailB() if err2 != nil { return err2 } Let's assume an error was returned. You realize from the error that there is a bug in the code. Now you're tasked with debugging the code given the error that was presented. Which function did the error come from? Who knows. And what if canFailA/canFailB return errors from other functions up the stack the same way? Now you've got a massive tree of possibilities to try and work through. A complete nightmare. In the real world you would take the error and do something with it. Even if you still end up returning an error, it won't be the error you received. It will be a new error that provides pertinent information about the situation. Go brought forth a legitimate "try" proposal that was very similar to the Rust example and, while well received on the surface, it failed because it was determined that you couldn't possibly use it, at least not beyond toy examples, because of the above. Presumably Go could introduce a concept of error (it currently has none) which could then include information like stack traces to help with that problem, but that's way more than what you're talking about, and would still lack all the other benefits you get when you handle errors as soon as you get them, not blindly pass them up the stack. Rust's solution may be nice for Rust, being designed for that pattern. It wouldn't fit well in Go without radically rethinking the language. > On the other hand, Go will let you forget to handle an error, if you do "res1, err1 = canFail()" but then forget to return an error yourself in the "err1" case. You can also forget to return res1 (per the original example). This is a real problem that should be solved, but it's not a problem of errors. It's a problem of values in general. Remember, the Go language has no inherit concept of error. Anything that we happen to call an error is actually just a user-defined type, same as any other type a user might define (birthdate, order number, stock price, etc.). To frame it as a problem of errors shows a misunderstanding of the problem.
- rad_gruchalski 4y ago> The Rust is interesting, for sure, but I don't know what "?" is doing. But it’s very easy to wrap one’s head around it. If the code under question mark returns an error, the function within which that code sits returns early with that error. If that function returns Result<T>, the question mark essentially provides short, composable code. I never personally had a problem with Go error handling but having prior experience with scala where I often used Try(…).toEither, I love Rust’s ?.
- randomdata 4y agoThe Rust code reads strangely, or at least inconsistently. Why are you returning res1 and res2 over having a magic operator handle them? I would expect something like: canFail()?! canFail()?! The Go code looks like at least some thought was put into the design, even if it is not the best code ever written.
- silisili 4y agoLess lines does not mean more readable. Anyone who has ever programmed will know exactly what Go is doing here(perhaps with exception to nil). Moreso if this were compilable code and every return returned two items as it needs to. You have to know Rust to understand what the Rust code is doing. Even then, it's not as obvious what's happening under the covers.
- kitkat_new 4y ago> You have to know Rust to understand what the Rust code is doing. don't see this as a problem > Even then, it's not as obvious what's happening under the covers. actually, it is, since it is clearly defined
- nirui 4y agoI like how currently Go handles errors. It's not just about res1, err1Error = canFail() it's also about res1, successful1Bool = canFail() and many other ways that communicates an abort event. Current Go syntax handles every case in a consistent way, while the Rust-like syntax could only apply to error type specifically. But you don't always have to return an error if something failed (say Hashmap lookup, strings.Index, or even just `if res1.IsValid()` without `err1` all together), right? Another thing to add is that, syntax sugar such as `canFail()?` cannot help you with situations when you need to do something if the error happened, say: res2, err2 = canFail() if err2 != nil { res1.Drop() return err2 } you still have to write the flow in the old way. Rust can do that safely because it can automatically/magically drop things, Go can't and don't. All and all, the application for sugars such as `canFail()?` is very narrow. IMO not worth to consider add.
- iainmerrick 4y agoBut you don't always have to return an error if something failed (say Hashmap lookup, strings.Index, or even just `if res1.IsValid()` without `err1` all together), right? No, a real error type is strictly better than a success/failure bool, I think. For something like a hashmap lookup, “false” becomes “NotFound” (say) which is a lot clearer. And you don’t need the “true” at all -- in that case you just have the result of the lookup. In your “res1.IsValid” example, would you still be returning a separate error result, just not using it? If so, that seems a little dangerous -- how can the compiler verify that you’re correctly handling errors? If instead there’s no explicit error return, it seems like you’re not really using the language’s built-in error handling, and it could work equally well in either Go or Rust.
- nirui 4y ago> would you still be returning a separate error result, just not using it It's a part of the examples for the statement "you don't always have to return an error", so... you don't return a separate error, you just abort at that point. Sorry I should have described it more clearly :)
- JustSomeNobody 4y agoBoth. Seriously. I'm not going to be reading Go code and all the sudden forget what error handling looks like.
- todotask 4y agoExactly that Go code is simple, comprehensible and readable. Your 2nd code is concise to me.