15. Functions as values

📖 Reading · 10 min
💡 Every code box below is live — edit it and hit Run.

In Go, a function is a value like any other. You can put one in a variable, pass it as an argument, return it from another function, and store it in a slice or a map. This is the foundation of a lot of idiomatic Go — sorting, HTTP handlers, middleware, callbacks.

A function in a variable

package main

import "fmt"

func double(n int) int {
    return n * 2
}

func main() {
    f := double
    fmt.Println(f(21))

    var g func(int) int = double
    fmt.Println(g(5))
}

Note double without parentheses — that's the function value itself. With parentheses you'd be calling it.

The type of that value is func(int) int: the word func, the parameter types, the result types. No names, just the shape. Any function with the same shape can be assigned to a variable of that type.

Anonymous functions

You can write a function without naming it, right where you need it:

package main

import "fmt"

func main() {
    square := func(n int) int {
        return n * n
    }
    fmt.Println(square(7))

    func(msg string) {
        fmt.Println("ran immediately:", msg)
    }("hello")
}

The second one is declared and called on the spot — note the ("hello") hanging off the closing brace. You'll see that shape most often with goroutines (go func() { ... }()) in the Concurrency module.

Passing a function as an argument

This is where it gets useful. A function that takes a function can leave a decision up to its caller:

package main

import "fmt"

func applyAll(nums []int, f func(int) int) []int {
    out := make([]int, 0, len(nums))
    for _, n := range nums {
        out = append(out, f(n))
    }
    return out
}

func main() {
    nums := []int{1, 2, 3, 4}

    fmt.Println(applyAll(nums, func(n int) int { return n * n }))
    fmt.Println(applyAll(nums, func(n int) int { return -n }))
}

applyAll knows how to walk a slice and collect results. It knows nothing about squaring or negating — that's the caller's business. One function, any transformation.

The same idea with a filter, where the passed function returns a bool (commonly called a predicate):

package main

import "fmt"

func filter(nums []int, keep func(int) bool) []int {
    var out []int
    for _, n := range nums {
        if keep(n) {
            out = append(out, n)
        }
    }
    return out
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
    evens := filter(nums, func(n int) bool { return n%2 == 0 })
    big := filter(nums, func(n int) bool { return n > 5 })
    fmt.Println(evens)
    fmt.Println(big)
}

Naming a function type

When a signature shows up repeatedly, give it a name with type:

package main

import "fmt"

type Validator func(string) error

func check(name string, v Validator) {
    if err := v(name); err != nil {
        fmt.Println(name, "->", err)
        return
    }
    fmt.Println(name, "-> ok")
}

func main() {
    notEmpty := func(s string) error {
        if s == "" {
            return fmt.Errorf("must not be empty")
        }
        return nil
    }

    check("brevfeed", notEmpty)
    check("", notEmpty)
}

Validator is now a real type. It reads better in signatures, and it gives you a place to hang documentation. The standard library does exactly this — http.HandlerFunc is a named function type.

A map of functions

Because functions are values, a map can hold them — a compact dispatch table:

package main

import "fmt"

func main() {
    ops := map[string]func(int, int) int{
        "add": func(a, b int) int { return a + b },
        "sub": func(a, b int) int { return a - b },
        "mul": func(a, b int) int { return a * b },
    }

    for _, name := range []string{"add", "sub", "mul"} {
        fmt.Printf("%s(6, 3) = %d\n", name, ops[name](6, 3))
    }
}

We loop over an explicit list of names rather than ranging the map directly, because map iteration order in Go is deliberately random. More on that in the Collections module.

Sorting with a function

The everyday payoff. sort.Slice takes your slice and a function that says whether element i should sort before element j:

package main

import (
    "fmt"
    "sort"
)

func main() {
    words := []string{"banana", "fig", "apple", "cherry"}

    sort.Slice(words, func(i, j int) bool { return words[i] < words[j] })
    fmt.Println("alphabetical:", words)

    sort.Slice(words, func(i, j int) bool { return len(words[i]) < len(words[j]) })
    fmt.Println("by length:   ", words)
}

One sort implementation, any ordering you can express — because the ordering is a value you hand in.

Your turn

Complete countIf, which takes a slice of ints and a predicate, and returns how many elements satisfy it. Call it with a predicate matching numbers greater than 10 so the program prints exactly:

3
package main

import "fmt"

func countIf(nums []int, match func(int) bool) int {
    // count the elements where match(n) is true
}

func main() {
    nums := []int{4, 12, 9, 30, 11, 2}
    fmt.Println(countIf(nums, func(n int) bool { return n > 10 }))
}
package main

import "fmt"

func countIf(nums []int, match func(int) bool) int {
    count := 0
    for _, n := range nums {
        if match(n) {
            count++
        }
    }
    return count
}

func main() {
    nums := []int{4, 12, 9, 30, 11, 2}
    fmt.Println(countIf(nums, func(n int) bool { return n > 10 }))
}

Next: what happens when one of these anonymous functions reaches out and grabs a variable from around it — closures.