Go language closure details
What is a closure? Closures are entities composed of functions and their associated reference environments.
Here are a few examples of closures in the Go language and the problems that arise from closure references.
Function variable (function value)
Before explaining closures, let’s take a look at what function variables are.
In the Go language, functions are treated as first-class values, which means that functions, like variables, have types, values, and can do anything normal variables can do.
func square(x int) {
println(x * x)
}
Copy the code
- Direct call:
square(1)
- Assign a function as if it were a variable:
s := square
; We can then call this function variable:s(1)
.Note: Heresquare
There are no parentheses after the call.
- call
nil
Can cause panic. - The zero value of the function variable is zero
nil
, which means it can follownil
Comparison, but not between two function variables.
closure
Now to illustrate closures with an example:
func incr(a) func(a) int {
var x int
return func(a) int {
x++
return x
}
}
Copy the code
Calling this function returns a function variable.
I := incr() : by assigning this function variable to I, I becomes a closure.
So I holds a reference to x, and you can imagine I has a pointer to x or an address in I that has x.
Since I has a pointer to x, x can be modified while remaining in the state:
println(i()) / / 1
println(i()) / / 2
println(i()) / / 3
Copy the code
That is, x escapes, and its life cycle does not end with the end of its scope.
But this code does not increment:
println(incr()()) / / 1
println(incr()()) / / 1
println(incr()()) / / 1
Copy the code
This is because incr() is called three times, returning three closures that refer to three different x’s, each of which has its own independent state.
Closure reference
Let’s start with an example of the problems caused by closure references:
x := 1
f := func(a) {
println(x)
}
x = 2
x = 3
f() / / 3
Copy the code
Because the closure refers to the outer layer lexical field variable, this code prints 3.
It can be imagined that f holds the address of x, which will be dereferenced directly when it uses x, so if the value of x changes, the value of f dereferenced will also change.
Instead, this code prints 1:
x := 1
func(a) {
println(x) / / 1
}()
x = 2
x = 3
Copy the code
It’s easy to understand when you put it in this form:
x := 1
f := func(a) {
println(x)
}
f() / / 1
x = 2
x = 3
Copy the code
This is because f is already dereferenced when it is called, and changes after that are irrelevant to it.
But if you call f again it will print 3, again proving that f holds the address of x.
We can prove this by printing the address of the referenced variable inside and outside the closure:
x := 1
func(a) {
println(&x) // 0xc0000de790} ()println(&x) // 0xc0000de790
Copy the code
You can see that the same address is referenced.
Loop closure references
The following three examples illustrate the problems caused by closure references within loops:
The first example
for i := 0; i < 3; i++ {
func(a) {
println(i) / / 0, 1, 2(1)}}Copy the code
This code is equivalent to:
for i := 0; i < 3; i++ {
f := func(a) {
println(i) / / 0, 1, 2
}
f()
}
Copy the code
After each iteration, I is referenced and the resulting value is used and not used again, so this code prints normally.
Second example
Normal code: output 0, 1, 2:
var dummy [3]int
for i := 0; i < len(dummy); i++ {
println(i) / / 0, 1, 2
}
Copy the code
This code, however, prints 3:
var dummy [3]int
var f func(a)
for i: = 0;i < len(dummy); i++ {
f = func(a) {
println(i)
}
}
f() / / 3
Copy the code
I talked about closures taking references, so this code should print the last value of I, 2, right?
Not right. That’s because the final value of I is not 2.
It is easy to understand the loop in this form:
var dummy [3]int
var f func(a)
for i: = 0;i < len(dummy); {
f = func(a) {
println(i)
}
i++
}
f() / / 3
Copy the code
I doesn’t get out of the loop until it reaches 3, so I ends up at 3.
So implementing this example with for range doesn’t work like this:
var dummy [3]int
var f func(a)
for i: =range dummy {
f = func(a) {
println(i)
}
}
f() / / 2
Copy the code
This is because of the underlying implementation differences between for range and for.
The third example
var funcSlice []func(a)
for i: = 0;i < 3; i++ {
funcSlice = append(funcSlice, func(a) {
println(i)
})
}
for j := 0; j < 3; j++ {
funcSlice[j]() / / 3, 3, 3
}
Copy the code
The output sequence is 3, 3, 3.
This is easy to understand: all three functions refer to the address of the same variable (I), so the value of dereference increases as I increases, so all three output 3.
Adding the code to the output address proves that:
var funcSlice []func(a)
for i: = 0;i < 3; i++ {
println(&i) // 0xc0000ac1d0 0xc0000ac1d0 0xc0000ac1d0
funcSlice = append(funcSlice, func(a) {
println(&i)
})
}
for j := 0; j < 3; j++ {
funcSlice[j]() // 0xc0000ac1d0 0xc0000ac1d0 0xc0000ac1d0
}
Copy the code
You can see that all three functions refer to the address of I.
The solution
1. Declare new variables:
- Declare a new variable:
j := i
And put the right afteri
Change the operation toj
Operation. - Declare a new variable with the same name:
i := i
.Note: the right-hand side of the declaration is the outer scopei
To the left is the newly declared scope of the layeri
. Same principle as above.
This is equivalent to declaring one variable for each of these functions, three of them, and each of them starts with a value that corresponds to I in the loop and doesn’t change from there.
2. Declare a new anonymous function and pass the parameter:
var funcSlice []func(a)
for i: = 0;i < 3; i++ {
func(i int) {
funcSlice = append(funcSlice, func(a) {
println(i)
})
}(i)
}
for j := 0; j < 3; j++ {
funcSlice[j]() / / 0, 1, 2
}
Copy the code
Now println(I) uses I passed in by function arguments, and function arguments in Go are passed by value.
So you’re declaring three variables in this new anonymous function that are referred to independently by the three closure functions. The principle is the same as the first method.
The solution here applies to most of the problems associated with closure references, not just the third example.
Refer to the link
Go Language Bible – Anonymous functions