Deep understanding of closures in go language



Closures are often used in functional programming. What are closures? How is it generated and what problem is it used to solve? First, the literal definition of closure is given: closure is an entity composed of function and its related reference environment (i.e., closure = function + reference environment). This is hard to understand literally, especially for programmers who have been programming in imperative languages.

Closures in the go language

First look at a demo:

func f(i int) func() int {
 return func() int {
 return i

Function f returns a function, which is a closure. The function itself does not define variable I, but refers to variable I in its environment (function f).

Let’s take a look at the effect:

c1 := f(0)
c2 := f(0)
c1() // reference to i, i = 0, return 1
c2() // reference to another i, i = 0, return 1

C1 and C2 refer to different environments. When I + + is called, they do not modify the same I, so the output of both times is 1. Every time function f enters, it forms a new environment. In the corresponding closure, the function is the same function, but the environment refers to different environments.

Variable I is a local variable in function F. it is not allowed to assume that this variable is allocated in the stack of function F. Because when function f returns, the corresponding stack will fail, and variable I in the function returned by function f refers to a failed position. Therefore, variables referenced in the environment of closures cannot be allocated on the stack.

escape analyze

Before continuing to explore the implementation of closures, take a look at one of the language features of go:

func f() *Cursor {
 var c Cursor
 c.X = 500
 return &c

Cursor is a structure, which is not allowed in C language, because variable C is allocated on the stack. When function f returns, the space of C fails. However, it is stated in the go language specification that this writing method is legal in the go language. The language automatically recognizes this and allocates C’s memory on the heap, not the stack of function F.

To verify this, observe the assembly code generated by function f:

Movq $type. "". Cursor + 0 (sb), (SP) // takes the type of variable C, that is, cursor
PCDATA $0,$16
PCDATA $1,$0
Call, runtime. New (sb) // call the new function, which is equivalent to new (cursor)
PCDATA $0,$-1
Movq 8 (SP), ax // take the address of c.x and put it in ax register
Movq $500, (AX) // assign the value of memory address stored in ax to 500
MOVQ AX,"".~r0+24(FP)

To recognize that variables need to be allocated on the heap is implemented by a compiler technology called escape analyze.

If you enter a command:

go build --gcflags=-m main.go

You can see the output:

Be careful:In the last two lines, mark C escapes and is moved to the heap. The scope of variables can be analyzed by escape analyze, which is an important technology for garbage collection.


The above is the whole content of this article. I hope that the content of this article can bring some help to your study or work. If you have any questions, you can leave a message for communication.

Recommended Today

Illustrated how to use cache correctly

Welcome to the official account of the personal public, “the back end school”, to learn and exchange weekly articles that are easy to understand and bring you quality articles, operating system, algorithms, etc., -Jvm, concurrent, Redis, etc. 1.1 Overview – caching is king over performance In the process of fighting against floods long ago, the […]