I imitated the online tutorial on closures and wrote the following code.
func foo1() func() {
xValue := 1
x := &xValue
defer func() {
xValue = 2
}()
return func() {
*x = *x + 1
fmt.Printf("foo1 val = %d\n", *x)
}
}
func main() {
f1 := foo1()
f1()
f1()
f1()
}
I am confused that after the exection of f1 := foo1()
, the variable xValue
seems like should be recycled, so the use of *x
should be wrong, but the above code has no errors and is executed fined, which gives the output
foo1 val = 3
foo1 val = 4
foo1 val = 5
So I wonder if the closure holds the value of the pointer in addition to the pointer itself or is the garbage collection mechanism of the Go language causing xValue to not be deleted?
In Go, a closure takes a reference to (an address of) any variable it closes over. To cite the language reference:
Function literals are closures: they may refer to variables defined in a surrounding function. Those variables are then shared between the surrounding function and the function literal, and they survive as long as they are accessible.
Hence, in your example:
f1 := foo1()
xValue
variable into existence (the compiler will likely allocate it on the heap). It will start out with the zero value for its type, 0.x
into existence and assigns it the address of xValue
.defer
-red closure runs and assigns to xValue
the value 2.x
.The latter point may be a bit tricky: since the returned closure references the variable x
, the compiler guarantees that variable exists even after foo
returned. Since x
contains an address of (and hence a live reference to) xValue
, that one still exists, too, and cannot be garbage-collected.
Using the same escape analysis approach, the compiler guarantees xValue
survives the return from the function it was declared in.
You execute the returned closure which modifies xValue
via the pointer to it–no magic happens here. The other two calls do the same.
All-in-all, maybe you got tripped by your knowledge of C++, where any variable declared in a function ceases to exist once the control is returned from that function, and hence any references to it existing outside of that function become invalid. In Go, that is not the case: the language is explicitly defined as safe in this regard: the compiler makes sure any variable has suitable allocation to survive the function call it was created in if a reference to it is returned (or otherwise communicated) from that function call to the outside world.