Pretty underwhelming for what's supposed to be a modern language.
Pretty underwhelming for what's supposed to be a modern language.
The key characteristic of Go's error handling is that you have to handle errors in the scope in which they occur, vs. exception handing which is designed around throwing errors up the stack until something finally handles it, often quite distant from the point of the error and lacking context.
"try" just re-spells that. It isn't a step towards exception handling; it's exactly as "exception-handle-y" as if err != nil { return err } already is, whatever value you may consider that to be. Part of the goal is to make correct handling where you actually do something with the error that much easier, instead of having to do something essentially unrefactorable for every error, through a combination of allowing error handling to be factorable in this new scheme, and some other changes to errors to add more structure by convention to create official ways of composing them together and examining these composed errors in sensible ways.
For example, consider a bug that causes a data structure invariant to be violated. The correct "handling" of the situation is to fix the bug and rerun the code, not add layers and layers of error "handling" code ahead of time.
def handle(request):
try:
return Response(200, process(request))
except UserError as ex:
return Response(400, ex.message)
except InternalError as ex:
return Response(500)
The principle stays the same, there is not much to "handle" and no option to recover without external help, either by providing a well-formed request for 400 errors or by providing well-behaving code for 500 errors.There are legitimate criticisms to be made of how Go's error handling works, but I think the language already handles the case you're talking about.
"Handling" an error includes further annotating it with information about why the code in question couldn't fix it, and this is probably the most common case. (I hedge only because we humans are actually really bad at judging such things, with our availability heuristic bias and other biases. I'm fairly confident this would indeed be the #1 case, but I've been wrong about this sort of thing in the past when I actually went to check.) We don't mean "fix" the error, just... "handle". Ideally you end up with a composite error object (as I said, in conjunction with a few other library-level things that are likely to get pulled up to official support and culture) that contains much more information about the error, and that if you do end up flinging it up to higher level code, you're leaving it with more options for understanding the resulting problems and dealing with them.
A. Create an error object, enter "error mode".
B. Annotate an error object with contextual information at each call point.
C. Return the error object up the call stack.
D. Translate from "error mode" into "value mode", producing the annotated error object as a value.
For languages with exceptions, B is automatic, but limited to stack traces, C is automatic. A and D are obtained via special language constructs, for example "raise ... / try: ... except E as ex: ...".
For Go [please excuse my almost total ignorance], B is manual, but arbitrarily expressive, C is manual, but terse via "try(...)", whereas A and D are done using a combination of standard language constructs and style conventions.
Assuming the above is correct, perhaps there is some reasonable design that automates B for most common use-cases. In particular, logging the invocation arguments at D makes it trivial to re-run the offending code in a debugger, with full stack trace and invocation arguments. Wrapping most function calls with "try(...)" is annoying, but manageable, whereas thinking what information should be carried by the error object on a case by case basis is a waste of brain cycles.
Go is only inevitable for those that need to deal with Docker and Kubernetes, the NoSQL hype successors.
Personally, I find Rust as more approachable and easier to wrap my head around opposed to Go. Though some of the syntax changes I don't like as much. Waiting on async/await to land in a couple months though.
I believe that after I fully integrate the Zen of Rust, that I will be writing multi-threaded programs with fewer bugs than I would in my alternative languages (C, C++, Go, Python, Lua).
The annoying things in Go that have accumulated in my mind over the last few years are all dealt with in a superior fashion in Rust today.
I'll still be using Go for some stuff at work, but I won't be starting personal projects in it, like I might have in the past.
Although to be fair to Go, I don't think ALGOL 68 had a (slightly broken) implementation of CSP and HTTP/2.0 support in the standard library.