Hammer
Hammer

Reputation: 1582

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!

Upvotes: 68

Views: 50236

Answers (5)

Younesious
Younesious

Reputation: 21

I think your main question, and the tricky one, is the c function.

For the a function, Initially, i has the zero value for int (which is 0), then it is explicitly set to 2. The return statement by default returns i (as specified in the function signature).

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

For the b function, i initially has a zero value, then it is set to 2. However, with the return 1 statement, we explicitly set i to 1 before the function returns to the caller, and i=1 is returned.

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

Now, let's dive into the c function. For the c function, we should pay attention to the defer keyword.
If we trace it line by line, here's what happens:

Based on the c function signature, i has the zero value of int, which is 0 in Go.
Referring to the official Go documentation, here’s a key facts about defer:

Each time a defer statement executes, the function value and parameters to the call are evaluated as usual and saved anew, but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. Deferred functions are executed after any result parameters are set by the return statement but before the function returns to its caller.

When the defer statement is executed on the first line of the function, i is evaluated to 0 as usual and saved anew i but the actual function (increment i) is not invoked.

The return 1 clause, set i to 1 as i is a named return value base on function signature.

Because defer captures a reference to i (not its value), it observes the final value of i after being set by the return statement. When the deferred closure invoked, it increments i by 1, resulting in i = 2.

Finally, the function returns i = 2.

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

Here’s a step-by-step trace of what happens:

  1. i has a zero value because it is specified in the result set.
  2. The return 1 statement sets i = 1.
  3. The deferred function is invoked, incrementing i++, so i = 2.
  4. The function returns i = 2.

Upvotes: 0

PokerFace
PokerFace

Reputation: 801

Maybe this named return value function

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

will be compiled like this:

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

Upvotes: 0

Roy Lee
Roy Lee

Reputation: 10862

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"

Upvotes: 64

Ahmed Osman
Ahmed Osman

Reputation: 318

I think the confusion is about function in function how about if you classified like this:

  func main() {
      fmt.Println(c()) //the result is 5
  }

  // the c function returned value is named j
  func c() (j int)  {
      defer changei(&j)
      return 6
  }
  func changei(j *int) {
      //now j is 6 because it was assigned by return statement 
      // and if i change guess what?! i changed the returned value
      *j--;
  }

but if the return value is not named like this:

  func main() {
      fmt.Println(c()) //the result will become 6
  }

  // the c function returned value is not named at this time
  func c() int  {
      j := 1
      defer changei(&j)
      return 6
  }
  func changei(j *int) {
      //now j = 1
      // and if i change guess what?! it will not effects the returned value
      *j--;
  }

I hope this will clear the confusion and that is how i did happy Go coding

Upvotes: 8

Mark
Mark

Reputation: 7101

According to the Go Specification:

Return Statements A "return" statement that specifies results sets the result parameters before any deferred functions are executed.

Defer Statements "...deferred functions are invoked immediately before the surrounding function returns..."

So yes, as you assumed, the named return variable is assigned, then the deferred statement increments it.

I would add that named return parameters can lead to subtle bugs, and generally should be avoided unless there's no alternative.

Upvotes: 13

Related Questions