2

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?

karsur
  • 65
  • 1
  • 7
  • You can get A -> B, but can not B -> A. – zhenhua32 Jul 28 '21 at 05:54
  • 1
    "So it should be the same, right?", No, not at all for defered functions. In the second example the _local_ variable err is changed but this doesn't propagate back to the caller. – Volker Jul 28 '21 at 06:44
  • @Volker: When you mean _local_ variable `err` in 2nd case, does it mean, in the 1st case of named returns, the values (result, err) do have a reference in main() ? Trying to understand how named returns work in the background – Inian Jul 28 '21 at 06:59
  • 1
    There is a technical way in which returned values are propagated back to the caller: Slots on the stack or registers or ... doesn't matter. _Named_ return values allow to access this slot. A named returned value just uses that slot a memory. When you do `return result, err` then `err` is _copied_ into that slot (and result too) but err has it's own memory. – Volker Jul 28 '21 at 08:02

3 Answers3

6

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.

mkopriva
  • 35,176
  • 4
  • 57
  • 71
  • Could you kindly reflect that logic upon the above problem in your answer? Because I see `result` does get modified in the 2nd case, but since the return values are discarded, the value for result remains at 0? – Inian Jul 28 '21 at 06:30
  • 1
    @Inian `bar()` panics, so `result = 100` does not get executed. `recover` does not automatically cause the panicked function to continue from where it left off. – mkopriva Jul 28 '21 at 06:36
5

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?

icza
  • 389,944
  • 63
  • 907
  • 827
0

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.
realcp1018
  • 399
  • 1
  • 3
  • 9