Functions, multiple returns, closures

Go · Essentials · 12 min

Multiple return values are how Go reports errors, and closures appear in callbacks, sorting and goroutines.

A function lists parameter names before types: func add(a, b int) int. Consecutive parameters of the same type can share it.

Go functions can return multiple values. The universal convention is a result plus an error: func parse(s string) (int, error). The caller unpacks both, n, err := parse(s), and uses _ to discard one it doesn't need. Results can also be named, func split(sum int) (x, y int); named results start at their zero values and a bare return returns them. Use names for documentation, but avoid bare returns in long functions — they hurt readability.

A variadic parameter, func sum(nums ...int), receives a slice. Call it with separate arguments, sum(1, 2, 3), or spread an existing slice with sum(xs...).

Arguments are always passed by value: the function gets a copy. (Slices, maps and pointers are small values that refer to shared data, which is why modifying a slice's elements is visible to the caller.)

Functions are first-class values. You can store them in variables, pass them as arguments (sort.Slice(xs, func(i, j int) bool {...})) and return them. A function literal that refers to variables of the enclosing function is a closure: it captures those variables themselves, not copies, so changes are shared and the variables live as long as the closure does.

Go has no default parameter values and no overloading; use distinct names or an options struct instead.

Example

package main

import (
	"errors"
	"fmt"
)

func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

func counter() func() int {
	n := 0
	return func() int { n++; return n } // closure captures n
}

func main() {
	if q, err := divide(1, 0); err != nil {
		fmt.Println("error:", err) // error: division by zero
	} else {
		fmt.Println(q)
	}
	next := counter()
	next()
	fmt.Println(next()) // 2
}

Remember

  • Return (result, error) and check the error at the call site
  • Variadic ...T arrives as a slice; spread a slice with xs...
  • Closures capture variables by reference, not by value

Watch out for

  • Ignoring a returned error by assigning it to _ without a good reason.

Check yourself

  1. `c := counter(); c(); c()` then `d := counter(); d()` — what does `d()` return (counter as in the example)?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: go.dev/ref/spec#Function_declarations