Colin's Journal: A place for thoughts about politics, software, and daily life.
I’ve found an increasing number of uses for closures in Go, to the point where they are one of my favourite features of the language. Closures are created when an anonymous function is declared and it references variables in the surrounding function, for example:
func main() { a := 0 b := func() { // a is shared between the main and anonymous function a++ } b() // Value of a is 1 fmt.Printf("Value of a is %v\n", a) }
Functions are first class types in Go, so a function can return a function that uses the variables and arguments of it’s parent. Such functions are a good alternative to producing a number of low value traditional struct{} types with a single method.
Closures also help in producing loosely coupled functions that serve as end points for a web service. Dependency Injection is used to provide the services required by the end point in a clearer way than defining end points as methods on a struct that holds all of the potential dependencies.
Closures are also good for creating iterators / generators, which don’t come up that often, but are a good way to encapsulate the complexity of navigating a complex data structure. By passing functions to functions that return functions, it’s possible to create simple micro-languages that make filtering and processing data records much easier to think about.
Finally closures enable in-line inversion of control in a very natural way. I’ve found this particular useful for iterating over SQL queries, significantly reducing boilerplate code and improving consistency of error handling.
While I’m clearly a big fan of using Closures in Go, you do have to be careful about exactly which variables you are capturing in a closure. For example:
func loop() { var funcs []func() int for i := 0; i < 5; i++ { funcs = append(funcs, func() int { return i }) } for _, f := range funcs { fmt.Printf("Func value: %v\n", f()) } }
The closures being created in this loop all capture the same variable i that is shared between them. As a result the output of the function is a series of 5s – the value of i at the end of the loop. If we create a new variable on each iteration of the loop and capture the value there, we get the desired behaviour of printing out 0,1,2,3,4:
func loop() {
var funcs []func() int
for i := 0; i < 5; i++ {
j := i
funcs = append(funcs, func() int {
return j
})
}
for _, f := range funcs {
fmt.Printf("Func value: %v\n", f())
}
}
The full list of my published Software
Email: colin at owlfish.com