Functions
Functions are the backbone of every Go program. Go's functions are more flexible than they first appear. Multiple return values, variadic args, closures, and named returns are all everyday tools. This chapter covers all of them.
Learning objectives
- Declare functions with typed parameters and return values.
- Return multiple values and use the idiomatic
(T, error)pattern. - Write variadic functions and know how to "spread" a slice into one.
- Use closures to capture variables from an enclosing scope.
- Use
deferfor clean-up and understand its LIFO order. - Recognize
panic/recoverand know when (not) to use them.
Declaring functions
func add(a int, b int) int {
return a + b
}
When consecutive parameters share a type, you can collapse:
func add(a, b int) int { return a + b }
Functions with no return have no type after the parentheses:
func greet(name string) {
fmt.Println("Hello,", name)
}
Multiple return values
This is one of Go's signature features. Functions commonly return value + error:
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
q, err := divide(10, 3)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("q =", q) // q = 3
}
The caller decides whether to handle or discard each return. This pattern is Go's replacement for exceptions (Chapter 23).
Named return values
You can name the return variables in the signature, giving you a "naked" return and better documentation:
func divmod(a, b int) (q, r int) {
q = a / b
r = a % b
return // naked return: returns q, r
}
Named returns are nice for documentation and for pre-declaring variables
you'll modify in a defer. Avoid using them just to skip
typing. A plain return q, r is often clearer in small functions.
Variadic functions
A ...T parameter accepts any number of T
values:
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum()) // 0 (no args is OK)
Inside the function, nums is a []int. To pass
an existing slice to a variadic function, use the spread operator
...:
xs := []int{1, 2, 3}
fmt.Println(sum(xs...)) // 6 (spread)
Common variadics you'll use all the time: fmt.Println,
append, fmt.Errorf.
Anonymous functions
Declare and use a function inline, without a name:
// Immediately invoked (IIFE)
func() { fmt.Println("hi") }()
// Assigned to a variable
greet := func(name string) { fmt.Println("Hello,", name) }
greet("Alice")
Closures
An anonymous function that captures variables from its enclosing scope is a closure. The captured variables persist for as long as the closure does:
package main
import "fmt"
func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
next := counter()
fmt.Println(next(), next(), next()) // 1 2 3
another := counter() // fresh counter
fmt.Println(another()) // 1
}
Each call to counter() creates a new n. That's
how closures give you tidy per-instance state without needing classes.
i := i. See Chapter 10.
First-class & higher-order
Functions are values. You can pass them, return them, and store them in slices or maps:
package main
import "fmt"
func apply(a, b int, op func(int, int) int) int {
return op(a, b)
}
func main() {
fmt.Println(apply(3, 4, func(x, y int) int { return x + y })) // 7
fmt.Println(apply(3, 4, func(x, y int) int { return x * y })) // 12
}
And functions that return functions:
func multiplier(factor int) func(int) int {
return func(x int) int { return x * factor }
}
double := multiplier(2)
triple := multiplier(3)
fmt.Println(double(5), triple(5)) // 10 15
Recursion
A function can call itself:
func fact(n int) int {
if n < 2 { return 1 }
return n * fact(n-1)
}
Go doesn't specially optimize tail calls, so very deep recursion can blow
the stack. For iterative problems, prefer a for loop.
defer
defer schedules a function call to run just before
the enclosing function returns, regardless of how it returns (normal
return, panic, or early return). It's Go's cleanup mechanism.
package main
import (
"fmt"
"os"
)
func readFile(path string) {
f, err := os.Open(path)
if err != nil { return }
defer f.Close() // guaranteed to run
// ... use f ...
fmt.Println("reading")
}
LIFO order
Multiple defers run in reverse order:
func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
}
// Output: 3 2 1
Arguments are evaluated immediately
The call expression is deferred; the arguments are evaluated right away:
i := 0
defer fmt.Println(i) // prints 0
i = 10
// function returns here, and fmt.Println runs with the captured i = 0
If you need the current value at defer time, use a closure:
defer func() { fmt.Println(i) }().
panic and recover
panic aborts the current goroutine and unwinds the stack,
running deferred functions as it goes. recover, used
inside a defer, stops the unwinding and returns whatever
was panicked with.
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
return a / b, nil // panics on b == 0
}
error
value instead. Chapter 23 on error handling explains the philosophy.
Check your understanding
Practice exercises
Min of any number of ints
Write a variadic function min(nums ...int) (int, bool)
that returns the minimum value and a boolean. If no args are
passed, return (0, false).
Show one possible solution
package main
import "fmt"
func min(nums ...int) (int, bool) {
if len(nums) == 0 {
return 0, false
}
best := nums[0]
for _, n := range nums[1:] {
if n < best {
best = n
}
}
return best, true
}
func main() {
v, ok := min(3, 1, 4, 1, 5, 9, 2, 6)
fmt.Println(v, ok) // 1 true
v, ok = min()
fmt.Println(v, ok) // 0 false
}
Go 1.21+ has a built-in min(), but writing your own
once teaches variadics nicely.
Timer closure
Write a function startTimer(name string) func() that
prints "name took duration" when the
returned function is called. Use it at the top of any function with
defer.
Hint: use time.Now() and time.Since().
Show one possible solution
package main
import (
"fmt"
"time"
)
func startTimer(name string) func() {
start := time.Now()
return func() {
fmt.Printf("%s took %s\n", name, time.Since(start))
}
}
func work() {
defer startTimer("work")()
time.Sleep(150 * time.Millisecond)
}
func main() {
work()
}
This is a classic closure idiom. Each call to startTimer
captures its own start in the returned closure.
defer ...() (note the parens!) calls the returned
closure when the surrounding function exits.
Further reading
Functions handled.