defer

defer schedules a call for when the current function finishes, which is how Go closes what it opens.

Syntaxdefer f(args) // args evaluated now, call runs when the function returns

When you open something you must close it, and a function can finish in several places. Remembering a close before every return is how resources get left open. defer solves this: put defer in front of a call and the call is held back until the surrounding function finishes, whichever way it finishes.

f, err := os.Open("data.txt")
if err != nil {
	return err
}
defer f.Close()

Write the close directly under the check that proves the open worked, and it cannot be forgotten. The two forms below behave the same, and the second needs no edit when you add a third return:

// longhand: close before every exit
if bad() {
	f.Close()
	return errBad
}
f.Close()
return nil

// shorthand: one defer covers every exit
defer f.Close()
if bad() {
	return errBad
}
return nil

Two rules that decide the result

First, the arguments are worked out at the moment defer runs, not later. defer fmt.Println(i) captures the value i has now, even if i changes afterwards.

Second, deferred calls run in reverse order: last deferred, first run. That matches how resources nest, so the thing opened last is closed first.

A deferred call also runs when the function stops because of a panic, an abrupt failure such as indexing past the end of a slice. That is covered in go-panic-recover.

Example

Example · go
package main

import "fmt"

func count() {
	i := 0
	// The value of i is copied into the call right now, as 0.
	defer fmt.Println("deferred saw:", i)
	i = 10
	fmt.Println("i is now:", i)
}

func main() {
	// These two run when main finishes, in reverse order.
	defer fmt.Println("third")
	defer fmt.Println("second")

	fmt.Println("first")
	count() // its own defer runs when count returns, not when main does
}

// Output:
// first
// i is now: 10
// deferred saw: 0
// second
// third

When to use it

  • Close a file or a network connection no matter which return the function takes.
  • Delete a temporary file once the work using it is finished.
  • Release something at the end of a function that has several early error returns.
  • Close layered writers in the reverse order they were created.

More examples

The closing idiom, in a whole program

The shape to copy whenever a function opens a file, a network connection or a database row set. The defer goes after the error check, never before it, because there is nothing to close when the open failed. Note that this function has three exits and only one close.

Example · go
package main

import (
	"bufio"
	"fmt"
	"os"
)

func countLines(path string) (int, error) {
	f, err := os.Open(path)
	if err != nil {
		return 0, err // nothing to close yet
	}
	defer f.Close() // runs on every return below

	n := 0
	sc := bufio.NewScanner(f)
	for sc.Scan() {
		n++
	}
	if err := sc.Err(); err != nil {
		return 0, err
	}
	return n, nil
}

func main() {
	const path = "/tmp/three-lines.txt"
	if err := os.WriteFile(path, []byte("a\nb\nc\n"), 0o600); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	n, err := countLines(path)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println("lines:", n)
}

// Output:
// lines: 3

Reverse order matches nested resources

Shows both halves of the rule. The outer file is deferred first so it closes last, which is the order the layers need. It also shows the limit of defer: for anything you write to, the Close can fail and lose data, so call it yourself and check the error rather than deferring it silently.

Example · go
package main

import (
	"compress/gzip"
	"fmt"
	"os"
)

func writeGzip(path, text string) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close() // deferred first, so it runs last

	zw := gzip.NewWriter(f)
	if _, err := zw.Write([]byte(text)); err != nil {
		return err
	}
	// The compressor holds buffered bytes, so closing it must happen
	// before the file closes, and its error must be reported.
	if err := zw.Close(); err != nil {
		return err
	}
	return nil
}

func main() {
	if err := writeGzip("/tmp/out.gz", "hello"); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println("written")
}

// Output:
// written

A defer in a loop, and the fix

The bug that turns into a production incident: a long loop over files or connections runs out of file handles, because defer waits for the function, not the iteration. Extracting the body into its own function gives each pass its own function exit, and the resource is released straight away.

Example · go
package main

import (
	"fmt"
	"os"
)

// Wrong: every file stays open until processAll returns.
func processAll(paths []string) error {
	for _, p := range paths {
		f, err := os.Open(p)
		if err != nil {
			return err
		}
		defer f.Close() // piles up, one per path
		fmt.Println("reading", f.Name())
	}
	return nil
}

// Right: the defer belongs to one small function, so it runs each pass.
func processOne(p string) error {
	f, err := os.Open(p)
	if err != nil {
		return err
	}
	defer f.Close()
	fmt.Println("reading", f.Name())
	return nil
}

func processAllFixed(paths []string) error {
	for _, p := range paths {
		if err := processOne(p); err != nil {
			return err
		}
	}
	return nil
}

Discussion

  • Be the first to comment on this lesson.