liveBook · Manning

Closure

As explained in 100 Go Mistakes and How to Avoid Them

A closure is an anonymous function that retains references to variables from its surrounding lexical scope.

A closure is an anonymous function that references variables outside its body. In Go, that can make an option function convenient—but it also means a deferred call or goroutine may observe a variable later than expected. section 2.11.3 section 6.6.1 section 9.3

Keep configuration with an option

`WithPort(port)` can return a closure that validates `port` and updates an options struct. The returned function retains access to the supplied port; `NewServer` later calls each option to build its configuration. This lets callers supply `WithPort(8080)` or omit options for the default configuration. section 2.11.3

Read the final status in a deferred call

A common mistake is `defer notify(status)` followed by assignments to `status`: the argument is evaluated when `defer` runs, so `notify` receives the earlier value. Use `defer func() { notify(status) }()` when notification needs the value at function return. A deferred closure can still receive an early value through an argument: `defer func(i int) { fmt.Println(i, j) }(i)` evaluates `i` immediately but reads the referenced `j` when the closure runs. Starting both at zero and then incrementing both prints `0 1`. Passing `&status` to a deferred function is another option if its signature can accept a pointer. section 6.6.1

Do not assume a goroutine sees the launch-time loop value

In the shown loop, `for _, i := range []int{1, 2, 3} { go func() { fmt.Print(i) }() }`, the closures refer to the same changing `i`. Execution may print `233` or `333`, rather than the values `123` in some order. Give each goroutine its intended value by creating `val := i` inside the loop and referencing `val`, or by calling `go func(val int) { fmt.Print(val) }(i)`. Neither fix promises an output order. section 9.3

A closure can also give `defer` a shorter surrounding function. When opening files in a loop, deferring `file.Close()` directly leaves each close pending until the outer function returns. Put one iteration in an immediately called closure so its defer runs at that iteration’s end. A separate helper function does the same job and may be clearer and easier to test. section 4.6

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Works through captured variables and evaluation timing with concrete fixes, without descending into runtime internals.
Examples
Traces a port option, deferred status notifications, loop-launched goroutines, and deferred file closure.
What it assumes
Assumes familiarity with Go functions and loops while defining closures and explaining their variable-evaluation behavior.
What you can do after
Diagnose/debug with it when deferred work reads a stale argument or goroutines observe a changing loop variable.
How it connects
Connects closures to functional options, defer, goroutines, and loop variables.

Also explained in

Compare how 20 books explain closure →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.