defer
defer schedules a call for when the current function finishes, which is how Go closes what it opens.
defer f(args) // args evaluated now, call runs when the function returnsWhen 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
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
// thirdWhen 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.
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: 3Reverse 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.
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:
// writtenA 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.
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