An observation is that the real problem here is the design of defer
and thus the answer should say that.
Motivating this answer, defer
currently needs to take exactly one level of nested stack from a lambda, and the runtime uses a particular side effect of this constraint to make a determination on whether recover()
returns nil or not.
Here's an example of this:
func b() {
defer func() { if recover() != nil { fmt.Printf("bad") } }()
}
func a() {
defer func() {
b()
if recover() != nil {
fmt.Printf("good")
}
}()
panic("error")
}
The recover()
in b()
should return nil.
In my opinion, a better choice would have been to say that defer
takes a function BODY, or block scope (rather than a function call,) as its argument. At that point, panic
and the recover()
return value could be tied to a particular stack frame, and any inner stack frame would have a nil
pancing context. Thus, it would look like this:
func b() {
defer { if recover() != nil { fmt.Printf("bad") } }
}
func a() {
defer {
b()
if recover() != nil {
fmt.Printf("good")
}
}
panic("error")
}
At this point, it's obvious that a()
is in a panicking state, but b()
is not, and any side effects like "being in the first stack frame of a deferred lambda" aren't necessary to correctly implement the runtime.
So, going against the grain here: The reason this doesn't work as might be expected, is a mistake in the design of the defer
keyword in the go language, that was worked around using non-obvious implementation detail side effects and then codified as such.
recover()
that way: maybe it'd be additional implementation work to allow baredefer recover()
, and more importantly, silently swallowing allpanic
s is risky in the way ignoringerr
is: it makes it much easier to have a problem that stops your program from doing what you want and not know what it is. – Anabel