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.

Go language closure details

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
  1. Direct call:square(1)
  2. Assign a function as if it were a variable:s := square; We can then call this function variable:s(1).Note: HeresquareThere are no parentheses after the call.
  • callnilCan cause panic.
  • The zero value of the function variable is zeronil, which means it can follownilComparison, 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 := iAnd put the right afteriChange the operation tojOperation.
  • Declare a new variable with the same name:i := i.Note: the right-hand side of the declaration is the outer scopeiTo 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