karsur
karsur

Reputation: 65

Why does go panic recover to return value with local variable not work?

This panic recover code works with named return values.

func main() {
    result, err := foo()
    fmt.Println("result:", result)
    if err != nil {
        fmt.Println("err:", err)
    }
}

func foo() (result int, err error) {
    defer func() {
        if e := recover(); e != nil {
            result = -1
            err = errors.New(e.(string))
        }
    }()
    bar()

    result = 100
    err = nil
    return
}

func bar() {
    panic("panic happened")
}

Output

result: -1
err: panic happened

But why this code with local variables does not work?

func main() {
    result, err := foo()
    fmt.Println("result:", result)
    if err != nil {
        fmt.Println("err:", err)
    }
}

func foo() (int, error) {
    var result int
    var err error
    defer func() {
        if e := recover(); e != nil {
            result = -1
            err = errors.New(e.(string))
        }
    }()
    bar()

    result = 100
    err = nil
    return result, err
}

func bar() {
    panic("panic happened")
}

Output

result: 0

Any explanation to help me understanding the reason / basic concept of it? In the go tour basics the explanation is as followed.

Named return values Go's return values may be named. If so, they are treated as variables defined at the top of the function.

So it should be the same, right?

Upvotes: 2

Views: 1748

Answers (3)

realcp1018
realcp1018

Reputation: 469

Might be a brief summary for @icza's anwser:

  1. Named return variables use their final values for returning when the function teminate with no panic(return normally or recover from panic), so you can change them in defer recover func(), and the final values changed, so be the return values.
  2. If use local variables, compiler can not know these local variables will be used as return variables until a normal return. Local variables might be changed in panic recover, but the return statement has not been executed yet because the panic, so the local variables you defined was not treated as return variables, the return values will be the zero values of the return types.

Upvotes: 0

icza
icza

Reputation: 418745

Spec: Return statements details this:

There are three ways to return values from a function with a result type:

  1. The return value or values may be explicitly listed in the "return" statement. Each expression must be single-valued and assignable to the corresponding element of the function's result type.
  2. The expression list in the "return" statement may be a single call to a multi-valued function. The effect is as if each value returned from that function were assigned to a temporary variable with the type of the respective value, followed by a "return" statement listing these variables, at which point the rules of the previous case apply.
  3. The expression list may be empty if the function's result type specifies names for its result parameters. The result parameters act as ordinary local variables and the function may assign values to them as necessary. The "return" statement returns the values of these variables.

So basically if you use a return statement that explicitly lists the return values, those will be used, regardless if the result parameters are named or not.

If the result parameters are named, they act as ordinary local variables: you can read and write them. If the result parameters are named, you may use a "naked" return statement, without listing the values to return. If you do so, then the actual return values will be the values of the (named) result parameters. The same thing applies if your function does not reach a return statement due to panicing and recovering: once the deferred functions run, the actual return values will be the values of the named result parameters (which the deferred functions can change and "have a say" in what to return).

If you don't use named result parameters but you declare local variables, they are not special in this way: when the function returns, those are not used "automatically" as the result values (like they would be if they would be named result parameters and not local variables). So if you change them in a deferred function, that will not have any effect on the actual values returned. In fact, if you don't use named result parameters and your function panics and recovers, you can't specify the return values, they will be the zero values of the result types. That's why you see result: 0 (0 is the zero value for int) and no error (because error is an interface type and zero value for interface types is nil and you don't print the error if it's nil).

See related: How to return a value in a Go function that panics?

Upvotes: 5

mkopriva
mkopriva

Reputation: 38343

Note that this has nothing to do with panic/recover, it is a feature of the defer statement.

... 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. If the deferred function has any return values, they are discarded when the function completes.

Upvotes: 6

Related Questions