package main
import (
"fmt"
"log"
)
func catch(err *error) {
if r := recover(); r != nil {
*err = fmt.Errorf("recovered panic: %v", r)
}
}
func panicIf42(n int) {
if n == 42 {
panic("42!")
}
}
func NormalReturns(n int) error {
var err error
defer catch(&err)
panicIf42(n)
return err
}
func NamedReturns(n int) (err error) {
defer catch(&err)
panicIf42(n)
return
}
func main() {
err := NamedReturns(42)
log.Printf("NamedReturns error: %v", err)
err = NormalReturns(42)
log.Printf("NormalReturns error: %v", err)
}
output:
2009/11/10 23:00:00 NamedReturns error: recovered panic: 42!
2009/11/10 23:00:00 NormalReturns error: <nil>
Playground link
NormalReturns returns a nil error, but I would expect both NamedReturns and NormalReturns to return a non-nil error.
I thought named returns was just a code readability feature that declares and initializes returns for you, but it seems there's more to it. What am I missing?
A return statement ends the execution of a function, and returns control to the calling function. Execution resumes in the calling function at the point immediately following the call. A return statement can return a value to the calling function.
If a function doesn't specify a return value, it returns None . In an if/then conditional statement, None evaluates to False. So in theory you could check the return value of this function for success/failure.
Unexpected Errors An unexpected error is something that would put the system into an unreasonable state or recovering is not possible without explicit handling of that case. Some examples of unexpected errors are: Divide by zero. In Go this will cause a panic.
Yes, it will end the method once a value is returned.
I thought named returns was just a code readability feature that declares and initializes returns for you, but it seems there's more to it. What am I missing?
If you name the result parameters, their actual value at the time of returning to the caller will determine the returned values. Meaning you can change their values like other local variables, and if the expression list of the return
statement is empty, their last assigned values will be used. Also if there are deferred functions, they can modify the values of the named result parameters after the return
statement and before the function returns to its caller, and those modifications will be preserved. It also allows to modify return values in case of a panic, see How to return a value in a Go function that panics?
Spec: Return statements:
Regardless of how they [the return values] are declared, all the result values are initialized to the zero values for their type upon entry to the function. A "return" statement that specifies results sets the result parameters before any deferred functions are executed.
And Spec: Defer statements:
For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned.
In NormalReturns()
: The return value is initialized to its zero value (which is nil
for all interface types, including the builtin error
type), and since the return
statement is not reached (due to a panic in panicIf42()
), it will stay nil
. It doesn't matter if the local variable err
is changed, that is not the result variable. It's just an ordinary variable. It will have no effect on the value returned
In general, if a function does not have named result variables, and if this function does not reach a return
statement (e.g. due to a panic), it cannot have return values other than (meaning different from) the zero values of the result types.
In NamedReturns()
the deferred catch()
will modify the named result variable err
. The changes are "preserved": whatever the named result variables hold will be returned when the function ends (which happens after calling deferred functions, if there are any). So even though the return
statement is not reached here either, the catch()
function changes the err
result variable, and whatever is assigned to it will be used as the value returned.
More on the topic:
Go blog: Defer, Panic and Recover:
Deferred functions may read and assign to the returning function's named return values.
And also in Effective Go: Recover:
If
doParse
panics, the recovery block will set the return value tonil
—deferred functions can modify named return values.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With