Kodewick
Start learning →
Lesson 16 of 24Contents

Function values and closures

Lesson 16 of 24 · Part 4 · Errors and functions · about 15 minutes

A function in Go is a value, like a number or a string. You can put it in a variable and call it through that variable:

prog.go
1package main2 3import "fmt"4 5func double(n int) int {6	return n * 27}8 9func square(n int) int {10	return n * n11}12 13func main() {14	f := double15	fmt.Println(f(5))16	f = square17	fmt.Println(f(5))18	fmt.Printf("%T\n", f)19}
Output
10
25
func(int) int

Note f := double has no parentheses: that's the function itself, not a call. Its type is func(int) int, "a function that takes an int and returns an int". Any function of that shape fits in f.

Functions as arguments

Since a function is a value, you can pass one to another function. apply doesn't know what it will do to each number; the caller decides:

prog.go
1package main2 3import "fmt"4 5func apply(nums []int, f func(int) int) []int {6	result := make([]int, len(nums))7	for i, n := range nums {8		result[i] = f(n)9	}10	return result11}12 13func double(n int) int {14	return n * 215}16 17func square(n int) int {18	return n * n19}20 21func main() {22	prices := []int{3, 5, 10}23	fmt.Println(apply(prices, double))24	fmt.Println(apply(prices, square))25}
Output
[6 10 20]
[9 25 100]

Anonymous functions

Writing a named function just to pass it once is a lot of ceremony. A function literal is a function written on the spot, with no name: func(n int) int { return n + 1 }. It can go straight into a call:

prog.go
1package main2 3import "fmt"4 5func apply(nums []int, f func(int) int) []int {6	result := make([]int, len(nums))7	for i, n := range nums {8		result[i] = f(n)9	}10	return result11}12 13func main() {14	prices := []int{20, 50, 100}15	withTax := apply(prices, func(p int) int {16		return p * 110 / 10017	})18	fmt.Println(withTax)19	half := func(n int) int { return n / 2 }20	fmt.Println(half(9), apply(prices, half))21}
Output
[22 55 110]
4 [10 25 50]

sort.Slice, again

Now the small function from lesson 10 makes sense. sort.Slice takes the slice and a function less(i, j int) bool that answers "should element i come before element j?". Change the function and you change the order:

prog.go
1package main2 3import (4	"fmt"5	"sort"6)7 8type Product struct {9	Name  string10	Price int11}12 13func main() {14	items := []Product{{Name: "lamp", Price: 30}, {Name: "cup", Price: 8}, {Name: "desk", Price: 120}}15	sort.Slice(items, func(i, j int) bool {16		return items[i].Price > items[j].Price17	})18	fmt.Println(items)19	sort.Slice(items, func(i, j int) bool {20		return items[i].Name < items[j].Name21	})22	fmt.Println(items)23}
Output
[{desk 120} {lamp 30} {cup 8}]
[{cup 8} {desk 120} {lamp 30}]

The first sorts by price, most expensive first; the second by name. The function uses items, a variable from main, which brings us to the next idea.

Closures

A function literal can use the variables around it, and not just read them: it works on the variable itself, not a copy. Such a function is called a closure. Press Step and watch total in main change each time add runs:

prog.go
1package main2 3import "fmt"4 5func main() {6	total := 07	add := func(price int) {8		total += price9	}10	add(12)11	add(30)12	add(8)13	fmt.Println("Total:", total)14}
Output
Total: 50

A counter that remembers

A closure keeps its variables alive even after the function that made them has returned. makeCounter creates count, returns a closure that uses it, and ends; the closure still has count. Press Step to watch count keep growing between calls:

prog.go
1package main2 3import "fmt"4 5func makeCounter() func() int {6	count := 07	return func() int {8		count++9		return count10	}11}12 13func main() {14	next := makeCounter()15	fmt.Println(next())16	fmt.Println(next())17	fmt.Println(next())18}
Output
1
2
3
Guess first

What does this show?

1package main2 3import "fmt"4 5func makeCounter() func() int {6	count := 07	return func() int {8		count++9		return count10	}11}12 13func main() {14	a := makeCounter()15	b := makeCounter()16	a()17	a()18	fmt.Println(a(), b())19}

A new variable each round

What if closures are made inside a loop? Since Go 1.22, a for loop gets a new loop variable each round, so each closure keeps its own i:

prog.go
1package main2 3import "fmt"4 5func main() {6	var later []func()7	for i := range 3 {8		later = append(later, func() {9			fmt.Println("job", i)10		})11	}12	for _, f := range later {13		f()14	}15}
Output
job 0
job 1
job 2

The functions run after the loop is over, and each still shows its own number. Before Go 1.22 the whole loop shared one i, and all three would have shown job 3; you may see that in older articles.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Filter with a function

Finish filter: return a new slice with only the numbers for which keep(n) is true. The program filters twice, with two different functions.

It should print
[4 8 42]
[15 23 42]
exercise1.go
1package main2 3import "fmt"4 5func filter(nums []int, keep func(int) bool) []int {6	var result []int7	return result8}9 10func main() {11	nums := []int{4, 15, 8, 23, 42, 7}12	fmt.Println(filter(nums, func(n int) bool { return n%2 == 0 }))13	fmt.Println(filter(nums, func(n int) bool { return n > 10 }))14}
Output
Press Run or Ctrl+Enter
Exercise 2

Two separate tabs

Finish makeTab: it returns a function that adds a price to a running total and returns the new total. Each tab keeps its own total.

It should print
Alice: 24
Bob: 15
exercise2.go
1package main2 3import "fmt"4 5func makeTab() func(int) int {6	return func(price int) int {7		return price8	}9}10 11func main() {12	alice := makeTab()13	bob := makeTab()14	alice(12)15	bob(5)16	alice(8)17	fmt.Println("Alice:", alice(4))18	fmt.Println("Bob:", bob(10))19}
Output
Press Run or Ctrl+Enter
Exercise 3

Fix it: the counter that starts over

Every call to next() shows 1. Move one line so the counter keeps counting: the program should show 1, 2, 3 for next and 1 for other.

It should print
1
2
3
1
exercise3.go
1package main2 3import "fmt"4 5func makeCounter() func() int {6	return func() int {7		count := 08		count++9		return count10	}11}12 13func main() {14	next := makeCounter()15	fmt.Println(next())16	fmt.Println(next())17	fmt.Println(next())18	other := makeCounter()19	fmt.Println(other())20}
Output
Press Run or Ctrl+Enter

What you learned

  • Functions are values: f := double (no parentheses); the type looks like func(int) int.
  • A function can take another function as an argument, like apply(nums, f) or sort.Slice(s, less).
  • A function literal func(…) … { … } has no name and can go straight into a call.
  • A closure uses the variables around it, the variables themselves, not copies.
  • A closure keeps its variables after the function that made them returns: each makeCounter() gets its own count.
  • Since Go 1.22, every round of a for loop has a new loop variable.
Finished this lesson?Mark it. Progress is saved in this browser, and you can undo it.