Understanding Go Closures and Delayed Binding: Real‑World Cases and Experiments
This article explores Go language closures and their delayed binding behavior through seven illustrative cases, detailed experiments, and code examples, clarifying value vs reference passing, goroutine timing effects, and practical pitfalls for developers.
I. Several Interesting Cases
Below are seven Go functions (foo1 to foo7) used to demonstrate closure concepts and related experiments.
func foo1(x *int) func() {
return func() {
*x = *x + 1
fmt.Printf("foo1 val = %d
", *x)
}
}
func foo2(x int) func() {
return func() {
x = x + 1
fmt.Printf("foo2 val = %d
", x)
}
}
func foo3() {
values := []int{1, 2, 3, 5}
for _, val := range values {
fmt.Printf("foo3 val = %d
", val)
}
}
func show(v interface{}) {
fmt.Printf("foo4 val = %v
", v)
}
func foo4() {
values := []int{1, 2, 3, 5}
for _, val := range values {
go show(val)
}
}
func foo5() {
values := []int{1, 2, 3, 5}
for _, val := range values {
go func() {
fmt.Printf("foo5 val = %v
", val)
}()
}
}
var foo6Chan = make(chan int, 10)
func foo6() {
for val := range foo6Chan {
go func() {
fmt.Printf("foo6 val = %d
", val)
}()
}
}
func foo7(x int) []func() {
var fs []func()
values := []int{1, 2, 3, 5}
for _, val := range values {
fs = append(fs, func() {
fmt.Printf("foo7 val = %d
", x+val)
})
}
return fs
}II. Case 1‑2: Value vs. Reference Passing
In Go, there is no true reference passing; even when a pointer is used (as in foo1), the function receives a copy of the pointer value. For explanatory purposes we refer to this as “reference passing”.
Both foo1 and foo2 return an anonymous function that increments a captured variable x and prints it. The returned functions are closures because they capture the surrounding environment.
Experiments:
// Setup
x := 133
f1 := foo1(&x) // closure capturing pointer to x
f2 := foo2(x) // closure capturing a copy of x
// Q1 – first group
f1()
f2()
f1()
f2()
// Change x
x = 233
// Q1 – second group
f1()
f2()
f1()
f2()
// Q1 – third group (direct calls)
foo1(&x)()
foo2(x)()
foo1(&x)()
foo2(x)()Results:
First group prints 134, 134, 135, 135.
Second group prints 234, 136, 235, 137.
Third group prints 236, 237, 237, 238, 238.
The difference arises because f1 captures the actual variable x (via pointer), so changes to x affect the closure, while f2 captures a copy.
III. Case 7: Closure Delayed Binding
The function foo7 builds a slice of closures inside a loop. All closures capture the loop variable val by reference, leading to delayed binding:
f7s := foo7(11)
for _, f7 := range f7s {
f7()
}All four calls print foo7 val = 16 because each closure sees the final value of val (5) when executed.
IV. Cases 3‑6: Goroutine Delayed Binding
Case 3 simply iterates a slice and prints values – no closure involved.
Case 4 launches a goroutine for each element using the helper show. The output order varies because goroutine scheduling is nondeterministic, but all values 1, 2, 3, 5 appear.
Case 5 launches an anonymous goroutine inside the loop. All four goroutines capture the same loop variable val, so they all print the last value (5).
Case 6 reads from a buffered channel and launches a goroutine for each received value. Experiments with different sleep intervals (nanoseconds, microseconds, milliseconds, seconds) show that when the producer is much faster than the consumer, the goroutine sees the most recent value, often printing the last element repeatedly.
V. Additional Experiments
A temporary case foo0 demonstrates that a closure sees the latest value of a captured variable after it has been modified before the closure is invoked:
func foo0() func() {
x := 1
f := func() {
fmt.Printf("foo0 val = %d
", x)
}
x = 11
return f
}
foo0()() // prints 11Another experiment ( foo8) measures the latency between spawning a goroutine and its execution, observing 5‑60 µs on a fast CPU and sometimes 0 µs on a slower one.
Overall, the article warns that while closures are powerful, careless use can lead to subtle bugs or memory leaks, and they should be employed judiciously.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
