Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can a normal return hide a panic that a named return correctly provides to the caller? [duplicate]

Tags:

go

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?

like image 755
vedran Avatar asked Dec 23 '19 21:12

vedran


People also ask

What is the purpose of the return statement in a function?

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.

What is returned by functions that don't have a return statement?

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.

When should we panic in Golang?

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.

Does a return statement end a method?

Yes, it will end the method once a value is returned.


1 Answers

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 to nil—deferred functions can modify named return values.

like image 116
icza Avatar answered Oct 17 '22 18:10

icza