Edit: see also icza's answer to Why can a normal return hide a panic that a named return correctly provides to the caller?
This is essentially a duplicate of How to return a value in a Go function that panics? However, the answer to that question just says that you must use a named return value, without explaining why you must use a named return value.
I've never found the Go spec here particularly clear myself. The description of return statements mentions three ways to return values from a function with a result type,1 but does not say anything explicit about panic and recover here. The rest is left to the section titled Handling panics:
While executing a function F
, an explicit call to panic or a run-time panic terminates the execution of F
. Any functions deferred ... [mostly snipped as not relevant here]. This termination sequence is called panicking.
[snippage]
The recover
function allows a program to manage behavior of a panicking goroutine. Suppose a function G
defers a function D
that calls recover and a panic occurs in a function on the same goroutine in which G
is executing. When the running of deferred functions reaches D
, the return value of D
's call to recover
will be the value passed to the call of panic
.
What all of this verbiage tells us is that we have to put the call to recover
inside a function that we call using defer
. That part is clear enough, and you've done it in your own code:
defer func() error {
if r := recover(); r != nil {
err = errors.New("SomeFunction: panicked!")
}
fmt.Println(err==nil)
return err
}()
Your deferred function—technically a closure—has a return value of type error
, but this return value is discarded because deferred functions are simply called with the values discarded (always). You might as well have written your closure to return nothing; this would function exactly the same way.
But your closure also captures the outer variable named err
, and assigns to it. Your goal here is clear enough, I think: you wanted this to be the return value from the function containing the closure. Alas, it won't be.
Let's return to the description of panic
-and-recover
now:
If D
returns normally, without starting a new panic, the panicking sequence stops. In that case, the state of functions called between G
and the call to panic is discarded, and normal execution resumes.
This too is a bit confusing. Let's think about it a moment. The final statement here is that normal execution resumes. But recover
got called only because deferred functions were being run. So normal execution, at this point, is already into the sequence of handling defer
s, in reverse order, popping them off the stack of defer
s for this particular function. In other words, we're already well in the middle of returning from function G
(the one that calls panic
).
There's one more statement left here before the spec goes into its own example:
Any functions deferred by G
before D
are then run and G
's execution terminates by returning to its caller.
This is just telling us that, indeed, the remaining defer
s get popped off the stack, one by one, and run. Function G
is, in other words, still in the middle of returning normally (no longer panicking).
The phrase a function with a result type simply means a function that returns some value(s), as opposed to, e.g., func f(args) { ... }
which returns no values.
What's missing here from the spec's example and/or return-value description
I mentioned above that the return statement description talked about three ways to return a value from a function:
- You can use a
return
statement with an expression list.
- You can use a
return
statement that returns a multi-valued function.
- You can have a function with named return values.
This third method is special. The spec doesn't come right out and say so, but we can infer it, because a return
with no expressions simply returns the values that are in the variables and the variables can be modified in deferred closures.
This last part is not described here but rather in the section on defer statements. Nonetheless, it is true. Since the deferred closure can modify the return value, that's how we do it. That's true for a deferred function that calls recover
as well. A return statement without values, such as:
func f() (a int, b string) {
a = 42
b = "what is six times nine"
return
}
sets a
and b
and then returns, and a return
with values:
func f() (a int, b string) {
return 42, "what is six times nine"
}
does exactly the same things. That is, first we assign, then we return. The return values can then be overridden by defer
s. If one of the deferred calls happens to catch a panic
by calling recover
, and that deferred call is to a closure that modifies the variable, that modification happens after the assignment and therefore changes the value of the variable, so that the return
returns the newly assigned value.
If we don't use a named return variable, though, there is no variable to modify. So for a recover
to change a return value, the only possible method is to use a named return variable.