Reputation: 1582
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:
- 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
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 thereturn
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:
i
has a zero value because it is specified in the result set.return 1
statement sets i = 1
.i++
, so i = 2
.i = 2
.Upvotes: 0
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
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:
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:
Upvotes: 64
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
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