4. Functions
A Go function declares its parameters and results, and both are part of its type. Functions are values, so they can be stored, passed, and returned. There are no default arguments, no keyword arguments, and no overloading: one name, one function, one signature.
4.1 Declarations
func power(base, n int) int
func bounds(xs []int) (lo, hi int, ok bool)
func log(format string, args ...any)
func run()
Consecutive parameters of the same type name it once. A single result needs no parentheses; two or more do. A function with no results omits the type entirely.
There is no forward declaration and no header file. A function may call one declared later in the file, or in another file of the same package, because the compiler reads the whole package before it checks anything.
Arguments are passed by value, always. The function gets a copy, and assigning to a parameter changes nothing the caller can see. When the argument is a slice, map, or pointer the copy refers to the same underlying data, so the function can modify what it points at; Chapter 5 is about that distinction and it is the one people get wrong.
4.2 Multiple Results
// Returning more than one value.
package main
import "fmt"
// bounds returns the smallest and largest of xs, and whether there were any.
func bounds(xs []int) (lo, hi int, ok bool) {
if len(xs) == 0 {
return 0, 0, false
}
lo, hi = xs[0], xs[0]
for _, x := range xs[1:] {
lo, hi = min(lo, x), max(hi, x)
}
return lo, hi, true
}
func main() {
if lo, hi, ok := bounds([]int{7, 2, 9, 4}); ok {
fmt.Println(lo, hi)
}
_, _, ok := bounds(nil)
fmt.Println("empty slice ok:", ok)
}
2 9
empty slice ok: false
Returning several values is ordinary in Go rather than a trick, and it is why
the language needs neither output parameters nor a tuple type. Two conventions
have hardened around it. A function that can fail returns its result and an
error last, the subject of Chapter 8. A function that looks something up
returns the value and a bool saying whether it was found, called the comma-ok
form, which you also get from a map index, a channel receive, and a type
assertion.
The results in bounds are named, which does two things: it documents which
is which in the signature, where a caller reading (int, int, bool) would have
to guess, and it declares those variables at the top of the function with their
zero values. A bare return then returns whatever they currently hold. That
last part is a trap in any function long enough that you cannot see the named
results and the return at once, so name results for documentation and write
the values out anyway, as bounds does.
min and max are built into the language and work on any ordered type.
4.3 Variadic Functions
// A variadic parameter collects the rest of the arguments into a slice.
package main
import "fmt"
func sum(label string, xs ...int) int {
total := 0
for _, x := range xs {
total += x
}
fmt.Printf("%s: %v -> ", label, xs)
return total
}
func main() {
fmt.Println(sum("three", 1, 2, 3))
fmt.Println(sum("none"))
xs := []int{4, 5, 6}
fmt.Println(sum("spread", xs...))
}
three: [1 2 3] -> 6
none: [] -> 0
spread: [4 5 6] -> 15
...int as the final parameter collects the remaining arguments into a
[]int. Inside the function it is an ordinary slice, empty rather than nil
when no arguments were passed. To pass a slice you already have, append ... to
it at the call site; without that, you would be passing one argument that
happens to be a slice, and the compiler will say so.
This is how fmt.Println and friends accept anything, with ...any as the
parameter. Chapter 9 explains what any is.
4.4 Defer
defer schedules a function call to run when the surrounding function returns,
whether it returns normally, returns early, or panics.
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
The close sits two lines from the open, so a reader checking whether the file is closed does not have to read to the end of the function, and adding an early return later cannot leak it. Every acquire-and-release pair in Go is written this way: unlock a mutex, close a file, stop a timer, restore a setting.
Three rules govern it.
// When deferred calls run, and what they see.
package main
import "fmt"
// order shows that deferred calls run last-in-first-out, and that their
// arguments are evaluated where the defer is written, not where it runs.
func order() {
i := 0
defer fmt.Println("first deferred, i was", i)
i = 1
defer fmt.Println("second deferred, i was", i)
i = 2
fmt.Println("body ends, i is", i)
}
// double uses a named result, which a deferred function can still change.
func double(n int) (result int) {
defer func() { result *= 2 }()
return n + 1
}
func main() {
order()
fmt.Println("double(3) =", double(3))
}
body ends, i is 2
second deferred, i was 1
first deferred, i was 0
double(3) = 8
Deferred calls run last-in-first-out, so releases happen in the reverse order of the acquisitions, as you want when they nest.
Arguments are evaluated where the defer is written, not where it runs.
i was 0 when the first defer was reached and 0 is what it printed, even
though i is 2 by the time the call happens. To defer a decision until the end
instead, defer a closure, as double does.
A deferred closure can modify named results. double returns n + 1, which
is assigned to result, and then the deferred function doubles it, so
double(3) is 8 rather than 4. This is how a function catches a panic and turns
it into an error return, and outside of that it is a good way to confuse
somebody.
The one place defer bites is inside a loop. It fires at the end of the
function, not the end of the iteration, so opening a thousand files in a loop
with defer f.Close() holds a thousand of them open. Move the body into its own
function when that happens.
4.5 Function Values and Closures
Functions are values with types. func(int) int is a type like any other, and a
variable of that type can hold any function with that signature. Its zero value
is nil, and calling a nil function panics.
// A function value carries the variables it refers to.
package main
import "fmt"
// counter returns a function that yields 1, 2, 3, ... on successive calls.
func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
next := counter()
fmt.Println(next(), next(), next())
other := counter()
fmt.Println(other(), next())
}
1 2 3
1 4
counter returns an anonymous function that refers to n, a local variable of
a function that has already returned. The variable survives because the closure
refers to it, and each call to counter makes a new one, so other starts
again at 1 while next carries on. Closures capture variables, not values, so
the two functions returned by two calls share nothing.
An anonymous function called immediately is written func() { ... }(), and the
trailing parentheses are what distinguish that from defining one.
4.6 Panic and Recover
panic stops the ordinary flow of a program: deferred calls run, the stack
unwinds, and the program prints the panic message and a stack trace and exits.
The runtime panics on your behalf for a bug that cannot be continued through, an
index out of range or a nil dereference among them.
recover, called inside a deferred function, stops the unwinding and returns
the value passed to panic.
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
Use this sparingly. Panics are for bugs, and errors are for the things that go wrong in a correct program, so a library that panics where it should return an error has moved a decision from the caller to itself. The reasonable uses are a package boundary where an internal recursive parser panics its way out of a malformed input, and a server that would rather lose one request than the whole process. Chapter 8 is about the other ninety-nine percent.
Exercise 4-1. Write func stats(xs []float64) (mean, stddev float64, ok bool).
Exercise 4-2. Write a function that takes a func(int) int and an int,
and returns a function applying the first n times.
Exercise 4-3. Predict what this prints, then run it:
for i := range 3 {
defer fmt.Print(i, " ")
}
Exercise 4-4. Write trace(name string) func() so that
defer trace("work")() prints an entry message immediately and an exit message
with the elapsed time when the function returns. Note where the parentheses go
and why.