CHAPTER 11 · CORE LANGUAGE

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 defer for clean-up and understand its LIFO order.
  • Recognize panic / recover and 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.

!
Capturing loop variables In Go 1.22+, loop variables are per-iteration, so the classic "closures capture the same variable" bug is fixed. On older Go versions, you had to rebind the variable inside the loop: 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
}
!
Don't use panic as control flow Panics are for truly unrecoverable situations (programmer bugs: impossible states, nil map writes, index out of range). For expected errors (file not found, bad input), return an error value instead. Chapter 23 on error handling explains the philosophy.

Check your understanding

Practice exercises

EXERCISE 1

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.

EXERCISE 2

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.