Function Values and Closures
In Go, functions are values: you can write them inline without a name, store them in variables, maps and structs, pass them to other functions and return them. An inline function that uses variables from the code around it is a closure — it keeps those variables alive and can change them. This lesson covers anonymous functions, function types, closures and the patterns built on them.
Anonymous functions
A function literal is written like a declaration without a name. You can assign it, pass it, or call it immediately:
package main import ( "fmt" "slices" "strings" ) func main() { shout := func(s string) string { return strings.ToUpper(s) + "!" } fmt.Println(shout("hello")) words := []string{"kiwi", "fig", "banana"} slices.SortFunc(words, func(a, b string) int { return len(a) - len(b) }) fmt.Println(words) func() { fmt.Println("called straight away") }() }
HELLO! [fig kiwi banana] called straight away
Passing a small function literal to another function — like the comparison for SortFunc — is the most common use.
Function types
Give a function signature a name with type to make code that passes functions around easier to read:
package main import "fmt" type Operation func(a, b float64) float64 var ops = map[string]Operation{ "+": func(a, b float64) float64 { return a + b }, "-": func(a, b float64) float64 { return a - b }, "*": func(a, b float64) float64 { return a * b }, "/": func(a, b float64) float64 { return a / b }, } func main() { for _, sym := range []string{"+", "*", "/", "%"} { op, ok := ops[sym] if !ok { fmt.Println(sym, "not supported") continue } fmt.Println(9, sym, 4, "=", op(9, 4)) } var nothing Operation fmt.Println(nothing == nil) // the zero value of a function type is nil }
9 + 4 = 13 9 * 4 = 36 9 / 4 = 2.25 % not supported true
Calling a nil function panics, so check before calling one that might not be set. Function values can be compared only with nil.
Closures
A function literal can use variables declared outside it. It doesn't copy them — it shares them, and keeps them alive for as long as the function itself exists:
package main import "fmt" func counter() func() int { count := 0 return func() int { count++ return count } } func main() { next := counter() fmt.Println(next(), next(), next()) other := counter() // a separate count variable fmt.Println(other(), next()) }
1 2 3 1 4
Each call to counter creates a new count variable and a function bound to it. The variable outlives counter's return because the closure still refers to it.
Closures that capture loop variables
Closures see the variable, not a snapshot of its value. Since Go 1.22, each loop iteration has its own variable, so closures created in a loop behave as you'd expect:
package main import "fmt" func main() { var actions []func() for _, name := range []string{"ana", "ben", "cleo"} { actions = append(actions, func() { fmt.Println("hello,", name) }) } for _, act := range actions { act() } total := 0 add := func(n int) { total += n } // modifies the outer variable add(5) add(10) fmt.Println("total:", total) }
hello, ana hello, ben hello, cleo total: 15
Functions that build functions
Returning a closure lets you create specialised functions from general ones:
package main import "fmt" func multiplier(factor float64) func(float64) float64 { return func(x float64) float64 { return x * factor } } func withPrefix(prefix string) func(string) string { return func(s string) string { return prefix + s } } func main() { double := multiplier(2) addVAT := multiplier(1.2) fmt.Println(double(21), addVAT(50)) warn := withPrefix("WARNING: ") fmt.Println(warn("disk almost full")) }
42 60 WARNING: disk almost full
Wrapping functions
A function that takes a function and returns a new one with extra behaviour is called a decorator or middleware. HTTP servers use this pattern heavily (lesson 61):
package main import ( "fmt" "strings" ) type Handler func(string) string func logged(name string, h Handler) Handler { return func(in string) string { out := h(in) fmt.Printf("%s(%q) = %q\n", name, in, out) return out } } func main() { upper := logged("upper", strings.ToUpper) trim := logged("trim", strings.TrimSpace) upper(trim(" go gopher ")) }
trim(" go gopher ") = "go gopher"
upper("go gopher") = "GO GOPHER"Notice that strings.ToUpper can be passed where a Handler is expected: its signature, func(string) string, matches the type.
Method values
A method bound to a particular value can also be used as a function value (methods are covered in lesson 38):
package main import ( "fmt" "strings" ) func main() { var b strings.Builder write := b.WriteString // bound to b write("method ") write("values") fmt.Println(b.String()) }
method values
Try it yourself
Write memoize, which wraps a slow function with a cache stored in a closure. Count how many times the real function runs.
16 25 16 16 25 36 real calls: 3
defer.