Conversely, the disadvantage with go is that errors have to be handled right away by the thing that may have caused it.
Sure, with a `try`, you may have no idea which line caused the error or why it did so, but that isn't what matters. After all, if a function returns an error, you don't know what line of code actually caused the error. (Especially in go, since errors lack backtraces.)
With "exceptions" you know that something within the block failed and passed the information about what went wrong to the catch/except/rescue block. Problems arise if the block fails to tie up its own lose ends, but that's a problem that exists without structured exceptions: if a function might return an error you still have to take it on faith that it arranged for clean-up of any state that, outside the function, would be problematic.
Fundamentally,
try {
foo()
bar()
} catch e Fred {
cleanup1()
} catch e Barney {
cleanup2()
}
isn't
really any different from
if e := (func()error{
if e := foo(); e != nil {
return e
}
if e := bar(); e != nil {
return e
}
return nil
})(); e != nil {
if _, ok := e.(Fred); ok {
cleanup1()
} else if _, ok := e.(Barney); ok {
cleanup2()
} else {
return e // assuming this function returns error
}
}
except that one of them is
far more readable.
It's potentially different, of course, inside foo(), where it might unexpectedly call a function which raises. But because recover exists in the language, robust functions must assume that any function call, or various built-in operations[1], might result in code further up the call stack recovering from a panic, so it must make sure it will fix its inconsistent state when the stack is unwound.
Additionally, go is inconsistent in that some errors - e.g. array index out-of-bounds - cause panics instead of indicating errors normally. That's understandable, since having to type
if c, ok := array[i]; ok {
err = fmt.Errorf("Array index out of bounds")
return
} else {
// do something with c
}
every time you wanted to do
c := array[i]
would be
really tiresome.
[1] Such as: comparing non-comparable objects via references of interface type; dereferencing a null pointer or interface; out-of-bounds array/slice/string indexing; division by zero; sending on a closed channel. All likely occurrences.