go

How does defer and named return value work?


I just started learning Go and I got confused with one example about using defer to change named return value in the The Go Blog - Defer, Panic, and Recover.

The example says:

  1. Deferred functions may read and assign to the returning function's named return values.

In this example, a deferred function increments the return value i after the surrounding function returns. Thus, this function returns 2:

func c() (i int) {
    defer func() { i++ }()
    return 1
}

But as what I have learned from A Tour of Go - Named return values

A return statement without arguments returns the named return values. This is known as a "naked" return.

I tested in the following code and in function b it returns 1 because it wasn't the "A return statement without arguments" case mentioned above.

func a() (i int) { // return 2
    i = 2
    return
}

func b() (i int) {  // return 1 
    i = 2
    return 1
}

So my question is in the first example, the surrounding function c has a named return value i, but the function c uses return 1 which in the second example we can see it should have return 1 no matter what value i is. But why after i changes in the deferred function the c function returns the value of i instead the value 1?

As I was typing my question, I might have guessed the answer. Is it because:

return 1 

is equals to:

i = 1
return 

in a function with a named return value variable i?

Please help me confirm, thanks!


Solution

  • A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. -- The Go Blog: Defer, Panic, and Recover

    Another way to understand the above statement:

    A defer statements pushes a function call onto a stack. The stack of saved calls popped out (LIFO) and deferred functions are invoked immediately before the surrounding function returns.

     func c() (i int) {
        defer func() { i++ }()
        return 1
    }
    

    After 1 is returned, the defer func() { i++ }() gets executed. Hence, in order of executions:

    1. i = 1 (return 1)
    2. i++ (defer func pop out from stack and executed)
    3. i == 2 (final result of named variable i)

    For understanding sake:

     func c() (i int) {
        defer func() { fmt.Println("third") }()
        defer func() { fmt.Println("second") }()
        defer func() { fmt.Println("first") }()
    
        return 1
    }
    

    Order of executions:

    1. i = 1 (return 1)
    2. "first"
    3. "second"
    4. "third"