Anders give an interview in 2003 [1] where he talks about how C# looked to learn from Java's checked exceptions. His conclusion was basically that, in their evaluation, 9/10 exceptions cannot be handled beyond some generic top-level handler.
If this observation is correct, and it certainly aligns perfectly with my own, then bubbling makes a lot more sense.
Note that, with error return values, you can emulate bubbling (which is what most Go programmers end up doing). And with exceptions, you can emulate return values. The question is what's the most common default? And, again, according to Anders, as well as any project I've ever worked on, bubble-by-default is overwhelmingly the most useful thing to support cleanly.
The only way Go's approach makes sense is if you consider it's original goal (system programming) and MAYBE (i don't know, I'm not a system programmer) for such systems you can/need to handle each error. Except that's not really how Go is being used now, so...
I've always been annoyed by the parallel control flow introduced by exceptions in any language. They are used so often in many languages where it doesn't feel necessary.
The fact that I don't even have to think if the function call I'm looking at can throw and if I should catch it or not outweighs everything.
Easy, just assume it throws. That's the case anyway. Thanks to panics, even in Go.
Edit: Also, there is no parallel control flow. Languages with exceptions have union-type return values, and every statement is implicitly followed by the equivalent of: if err!=nil return nil, err. The fact that in Go you have to type that makes Go cumbersome, not smart.
Yes, exactly like that. In real world programs, every function might fail, even the simplest ones (stack overflow, out of memory, interrupts, etc). No information is gained by declaring a specific function might fail. Also it's almost certainly a lie to declare: this function will never fail. So if every function might fail, why not just produce a union type <Result|ErrorData> for every function return type. Also, lets automatically check for the error case after each nested invocation, and cleanly unwind the stack (returning ErrorData again) on failure. This makes 95% error handling code go away. The ErrorData type uses a special return keyword ("throw"), and in rare cases, errors need to actually be handled, so the union's ErrorData type is exposed to the user code with additional primitives (catch). On all the code in between, the ErrorData type is just hidden behind the scenes.
Not really, in all the years I've been writing Go, only one library used panics for error handling.
Usually if something panics you don't want to handle it. (Other than at the http handler level, where you can just throw an InternalServerError and log the panic)
First and foremost, you can usually assume libraries won't panic, though it would be nice to have a tool (grep) to check for explicit panics.
Actually, you often do. That's the point really. You should decide if it's an operation you might want to retry, you might also want to just flat out error and do nothing more, maybe you want to provide degraded functionality, like provide some default answer.
I think errors as values cause you to always think about this, which makes you handle errors in a more sensible way, instead of just bubbling up. Sure, 90% of situations you will bubble up, but in my opinion it's still worth it.
The thing that bugs me is that if you ignore the error (by simply not checking for it), it's still there, possibly insidiously corrupting runtime state. Imagine trying to debug a file format corruption that happened because some obscure part of the code tried to add to the format and instead errored (silently) and added garbage and then the code just kept chugging along until the state REALLY messed things up.
The thing many programmers don't seem to realize is that a program is a model of a design in the programmer's mind. If the model goes off the rails of the expected design/behavior in any way, that should be considered very bad ASAP... or as many languages treat it, "exceptional".
I used to forget to put "set -e" in bash scripts. Then one went off and deleted a whole bunch of important stuff despite a prerequisite command erroring. Now I include it, but remembering one line of code in the header is easy compared to Go's approach of remembering to check every error.
Anders give an interview in 2003 [1] where he talks about how C# looked to learn from Java's checked exceptions. His conclusion was basically that, in their evaluation, 9/10 exceptions cannot be handled beyond some generic top-level handler.
If this observation is correct, and it certainly aligns perfectly with my own, then bubbling makes a lot more sense.
Note that, with error return values, you can emulate bubbling (which is what most Go programmers end up doing). And with exceptions, you can emulate return values. The question is what's the most common default? And, again, according to Anders, as well as any project I've ever worked on, bubble-by-default is overwhelmingly the most useful thing to support cleanly.
The only way Go's approach makes sense is if you consider it's original goal (system programming) and MAYBE (i don't know, I'm not a system programmer) for such systems you can/need to handle each error. Except that's not really how Go is being used now, so...
[1] https://www.artima.com/intv/handcuffs.html