I wonder if they would have spent more thoughts on error handling in a language that defaults to crashing on error (e.g. Java's exceptions)
I wonder if they would have spent more thoughts on error handling in a language that defaults to crashing on error (e.g. Java's exceptions)
However, Java has checked Exceptions which would force you to handle the possibility explicitly or the code would not compile, but the Java experience shows that almost always, people will just do:
try {
sendMail();
} catch (CannotSendMailException e) {
throw new RuntimeException(e);
}
Or even just log the Exception (Which would actually be the right thing to do in the case of the study!).With Go's multiple return values to represent errors, they are also known to be too easy to "forget" to even look at the error value.
With Rust's approach, which is using sum types to represent either success or error (with the `Result` type), it is impossible to forget to check, like Java checked Exceptions... but just like in Java, arguably even easier, you can just sort of ignore it by calling `unwrap()`, which is fairly common to do even in production-level Rust code (it's not always wrong, some very prominent people in the Rust community have made that point very clearly... but in many cases that's done out of laziness rather than thought), and is essentially equivalent to PHP `or die` because it will panic if the result was an error.
Maybe a better example of the opposite would be python, with its unchecked exceptions. One of my first thoughts in python error handling is "here I don't want exceptions to propagate, let's throw in a lazy `try ... except print(...); sleep(1)`.
But I'm not sure I actually do that more than e.g. in rust, simply because I write them in so different environments (my python code just has to run and produce correct results, my rust code is rolled out to customers and has to pass code review)
Like if one could easily specify that within a certain scope (method or try-block), any of a list of exception classes (checked or unchecked) will become automatically wrapped into a target checked exception class as the chained "cause."
So you could set a policy that EngineBrokenException=or OutOfFuelException bubbling up will become FleetVehicleInoperableException.
In this case, I think the actual API we used would take a callback for success, and a callback for errors. I just used JS an example for how unnatural it would be to call something that exits the entire script early.
I have a big problem with promises + exceptions generally in JavaScript - much preferring union types to represent errors instead of allowing things to go unchecked. But I left that out as it was kind of a side-note from the point of affordance.
throw new RuntimeException(e);
Linters like SpotBugs tend to complain about that. But it’s also a matter of naming. If RuntimeException was instead called something like ProgramBug, people might be more reluctant.In the end, it’s an issue of education. People need to be aware of the possible consequences, and have to be taught how to properly handle such cases. There is no way to automate error handling without the developer having to think about it, because the right thing to do is context-dependent. Checked exceptions at least make the developer aware of failure modes to consider.
How? Unassigned
foo := myFunc() # [...] assignment mismatch: 1 variable but myFunc returns 2 values
Assigned, but not used foo, bar := myFunc()
fmt.Println(foo) # [...] declared and not used: bar
Intentionally ignored foo, _ := myFunc()
Ignoring is a deliberate decision and trivial to review, lint and grep for.Except for, I suppose, shadowing a variable before checking it
foo, bar := myFunc()
_, bar = myFunc()
fmt.Println(foo, bar)
Which is more of a general grievance of quite some people, but I don't think that has much to do with multiple returns - and you definitely have to look at the error to pave over it afterwards.Of course, it can't prevent people from pasting an error handler everywhere instead of thinking about it, which I think are the same people who hate Go's error handling
In Java with checked exception you have no choice, you either try/catch it or you throw(s) it and are forced to handle it or its a compile error. Java does this aspect better, error handling features are relatively weak in Go but people have utilised the multiple returns well to make the best of it.
You don't have to give any consideration to that if you don't want to; you can always just catch the exception and rethrow it as a RuntimeException.
Rust has almost the same error handling concept, but with way less boilerplate.
And Rust actually syntactically forces to handle the error case, because you can't just access the return value when there are potential errors
You certainly must handle the error if you want your computation to continue.
> golang is more explicit
Not sure how golang is more explicit. In functor-based style, an error-prone computation stops if it yields an error and that error isn't handled explicitly. That makes sure that any successful computation is based on expected behaviour from beginning to end.
> and robust
Likewise, that's a claim based on nothing. Forcing developers to write a little snippet of code everywhere lest their code has a bug does not make code more robust.
> but I have definitely seen more ”extreme” and correct error handling in golang, whereas in rust the convenience of just bubbling it up wins.
You also have the option to match a result for lower-level error handling in rust.
Claiming that "convenience" makes rustaceans not use that option is like claiming that gophers don't check the error content because it's faster to panic.
You're right that the languages have the same capability. The terseness can IMO both be a strength and a liability.
I am not even sure if Rust is that more convenient in this regard generally. To actually be able to use a ?, you have to actually define a conversion for the error types (which again you define yourself) or explicitly opt into a solution like anyhow, which would allow to use them almost blindly.
In Go, you can just blindly put your boilerplate (potentially using IDE shortcuts/autocomplete as some suggested to me or told me about).
> whereas in rust the convenience of just bubbling it up wins
Not necessarily, I have seen so many ways of error handling at this point, and what is best probably may depend on the individual situation.
In my personal experience, neither using anyhow nor my current default approach (custom error struct and `map_err(Error::EnumVariant)?` have prevented me from acknowledging that a potential error and thinking about if I should handle it in the current function or bubble up, although I agree that the perceived cost of handling an error may be increased, because you add comparatively more code in Rust.
Further, I feel like what Rust does is already the upper limit of boiler plate I can tolerate, although I am not sure there is a better way of this type of error handling.
Part of it is the syntactical easiness of "?", how can any other solution compete with the ease of typing a single character?
But mostly, it's that "fmt.Errorf" is built-in and widely used, as opposed to whatever library you need to go out of your way to choose to be able to easily annotate errors in Rust.