A study of Go language closures

Closure is a very abstract concept in Go language, which is also the first difficulty encountered by the author in the learning process of Go language. I hope this paper can introduce the concept and characteristics of closure clearly in detail.

Let’s start with function variables

In Go language, a function is also a variable, which has type, value, address, can be assigned, referenced, etc. The zero value of a function is nil, but functions cannot be compared.

What is a closure

Because the function is also variable, natural can think function also is to have the address (for assembly and understand will know after the operating system to a certain extent, every function call in memory for this allocates an area, is called a stack frame), and closure, can be understood as the function of address assigned to a variable, and due to the variable will automatically Go back references, So you can use this variable as a function (and because of the concept of stack frames, it also holds the state of the variables inside the function when it is assigned). Here is an example to illustrate it:

package main

import "fmt"

func inc(a) func(a) int {	// The return value is a function closure
    var x int
    return func(a) int {
        x++
        return x
    }
}

func main(a){
    i := incr()	// I is assigned to the function closure, which holds the state of x
    fmt.printLn(i())	/ / 1
    fmt.printLn(i())	/ / 2
    fmt.printLn(i())	/ / 3
    
    fmt.printLn(incr()())	/ / 1
    fmt.printLn(incr()())	/ / 1
    fmt.printLn(incr()())	/ / 1
    
    return
}
Copy the code

In the first three outputs, since I holds the state of X, each call to I changes the value of X through the pointer to X stored in I, and the state continues to be stored in I. This state is called the escape of X, and its life cycle does not end with the end of the scope.

In the following three outputs, each time a closure returned by incr() was called, the x of the three outputs belonged to different stack frames with independent states.

Closure reference

Once you understand the definition of closures, references to closures come naturally. A reference to a closure is just like a reference to any other variable, except that the result is a function that holds the state of the closure at the time it was declared. It is important to note that closures are referential to outer lexical field variables, that is, changes to variables outside the closure may affect values inside the closure, as illustrated in the following example:

x := 1
f := func(a) {
	println(x)
}()	// This is equivalent to calling f() once after the definition, printing 1
x = 2
f()	/ / 2
x = 3
f()	/ / 3
Copy the code

We dereference x every time we call f, because the closure holds the address of x.

Advanced instance

To further understand the nature of closures, here is a more complex example involving circular references to closures:

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]()
}
Copy the code

The reader can start by thinking about what the output of this code will be.

5

4

3

2

1

0

The final output of this code is 3, 3, 3.

Feeling a little confused? This is normal, so let’s examine this code.

The first is the declaration of the slice variable funcSlice, through a for loop, each time adding a closure to the end that prints the variable I.

If you look at the definition of this closure, is it the same as the function that printed x? So I is also a reference, and the closure actually holds the address of I, except that when the closure function is called, it automatically dereferences the value it holds. You can modify this code as follows:

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]()
}
Copy the code

The output is 0xC0000AC1d0 0xC0000AC1d0 0xC0000AC1d0, that is, the address of I is the same each time the closure is declared, so the final output of I is the value 3 given to I after the first for loop ends.

So what if I want the output to be 1, 2, 3? Here are two solutions that you can use when you encounter most closure reference problems.

Declare new variables

Declare a new variable in the returned closure: Output := I, and print j so that instead of a reference to I, Output is Output. The modified code is:

var funcSlice []func(a)
for i := 0; i < 3; i++ {
	funcSlice = append(funcSlice, func(a) {
        Output := i
		println(Output)
	})
}
for j := 0; j < 3; j++ {
	funcSlice[j]()
}
Copy the code

Declare a new function and pass it as an argument

Change the code to:

var funcSlice []func(a)
for i := 0; i < 3; i++ {
	func(i int) {
		funcSlice = append(funcSlice, func(a) {
			println(i)
		})	/ / closures
	}(i)	// Close and call
}
for j := 0; j < 3; j++ {
	funcSlice[j]()	/ / call
}
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.

To consider

Hopefully this blog will give you a good grasp of closures in the Go language.

You can try to use closures for yourself by implementing a Fibonacci function that returns a closure that returns a continuous Fibonacci number.

Refer to the link

Go language closure details