Closures are a powerful feature of programming languages including Go. With closures, you can encapsulate data in a function and access it through the return value of the function. In this article, we will cover the basics of closures in Go, including what they are, how they work, and how to use them effectively.
What is a closure
There is an official explanation from go:
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.
Translated:
Function literals (anonymous functions) are closures: they can refer to variables defined in surrounding functions. These variables are then shared between the surrounding function and function literals, and they continue to exist as long as they are still accessible.
A closure is a way of creating functions that can access variables defined outside of their bodies. A closure is a function that can capture the state of its surroundings. This means that a function can access variables that are not defined in its parameter list or in its body.Closure functions can access these variables after external functions return。
Create closures in Go
In Go, you can create closures using anonymous functions. When a closure is created, the function captures the state of its surroundings, including any variables defined in the external function. Closure functions can access these variables after the external function returns.
Here is an example of creating a closure in Go:
func adder() func(int) int { // External functions sum := 0 return func(x int) int { // Internal functions ("func sum: ", sum) sum += x return sum } } func main() { a := adder() (a(1)) (a(2)) (a(3)) }
In this example, we define an adder function that returns an anonymous function. Anonymous function captures the defined in the adder functionsum
The state of the variable. Each time an anonymous function is called, it adds the parameters to the sum variable and returns the result.
So its output is:
func sum: 0
1
func sum: 1
3
func sum: 3
6
Using closures in Go
In Go, closures can be used for a variety of purposes, including encapsulating data with functions, creating generators, iterators, and memoization functions.
Here is an example of using closures to encapsulate data with functions:
func makeGreeter(greeting string) func(string) string { return func(name string) string { ("func greeting: %s, name: %s\n", greeting, name) return greeting + ", " + name } } func main() { englishGreeter := makeGreeter("Hello") spanishGreeter := makeGreeter("Hola") (englishGreeter("John")) (englishGreeter("Tim")) (spanishGreeter("Juan")) (spanishGreeter("Taylor")) }
In this case, we define a name calledmakeGreeter
function, which returns an anonymous function. The anonymous function takes a string parameter and returns a string that concatenates the greeting and name. We created two greeting programs, one for English and one for Spanish, and then called them with different names.
So its output is:
func greeting: Hello, name: John
Hello, John
func greeting: Hello, name: Tim
Hello, Tim
func greeting: Hola, name: Juan
Hola, Juan
func greeting: Hola, name: Taylor
Hola, Taylor
Replace captured variables
One of the powerful features of Go closures is the ability to change captured variables. This makes the behavior in the code more flexible and dynamic. Here is an example:
func makeCounter() func() int { i := 0 return func() int { ("func i: ", i) i++ return i } } func main() { counter := makeCounter() (counter()) (counter()) (counter()) }
In this case,makeCounter
The function returns a closure, and each call increments the counter. The i variable is captured by the closure and can be modified to update the counter.
So its output is:
func i: 0
1
func i: 1
2
func i: 2
3
Escape variables
Another advanced concept of Go closures is variable escape analysis. In Go, variables are usually allocated on the stack and de-allocated when out of scope. However,When a variable is captured by a closure, it must be allocated on the heapto ensure that it can be accessed after the function returns. This leads to performance overhead, so it is important to understand when and how variables escape.
Let's compare two methods:
func makeAdder1(x1 int) func(int) int { return func(y1 int) int { return x1 + y1 } } func makeAdder2(x2 int) func(int) int { (x2) return func(y2 int) int { return x2 + y2 } } func main() { a := makeAdder1(5) (a(1)) b := makeAdder2(6) (b(1)) }
makeAdder1
andmakeAdder2
The difference is in the functionx
Whether it is used.
And we analyze through escape:
go build -gcflags "-m"
The following output will be obtained:
./:5:6: can inline makeAdder1
./:6:9: can inline makeAdder1.func1
./:13:9: can inline makeAdder2.func1
./:12:13: inlining call to
./:19:17: inlining call to makeAdder1
./:6:9: can inline main.makeAdder1.func1
./:20:15: inlining call to main.makeAdder1.func1
./:20:13: inlining call to
./:23:13: inlining call to
./:6:9: func literal escapes to heap
./:12:13: ... argument does not escape
./:12:14: x2 escapes to heap
./:13:9: func literal escapes to heap
./:19:17: func literal does not escape
./:20:13: ... argument does not escape
./:20:15: ~R0 escapes to heap
./:23:13: ... argument does not escape
./:23:15: b(1) escapes to heap
Judging from the escape analysis results,x
Variables are captured by closures and must be allocated on the heap. However, ifx
Variables are not used by any other code other than closures, and the compiler can optimize and assign them to the stack.
Shared closure
Finally, closures in Go can be shared among multiple functions, enabling greater flexibility and modular code. Here is an example:
type Calculator struct { add func(int, int) int } func NewCalculator() *Calculator { c := &Calculator{} = func(x, y int) int { ("func x: %d, y: %d\n", x, y) return x + y } return c } func (c *Calculator) Add(x, y int) int { return (x, y) } func main() { calc := NewCalculator() ((1, 2)) ((2, 3)) }
In this case,Calculator
The structure has aadd
The function, which is inNewCalculator
The function is initialized by closure.Calculator
StructuralAdd
The method only needs to be calledadd
function, so that it can be reused in multiple contexts.
So its output is:
func x: 1, y: 2
3
func x: 2, y: 3
5
in conclusion
In Go programming, closures are a powerful tool that can be used to encapsulate data with functions, create generators and iterators, etc. They provide a way to access variables defined in a function in vitro, even after the function returns.
This is the article about briefly analyzing the creation and use of closures in Golang. For more related go closure content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!