defer, in a nutshell

When learning Go, one quickly comes across the defer keyword. For instance, the Tour of Go introduces defer thus:

A defer statement defers the execution of a function until the surrounding function returns.

The deferred call’s arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.

package main

import "fmt"

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

defer provides a convenient way of ensuring that some piece of code unconditionally gets executed before the enclosing function returns control to its caller.

Often, you’ll write a function that acquires some resources (file descriptor, mutex, etc.). Such a function must typically release those resources before returning, regardless of the execution path—your function may contain numerous return statements indeed. Failing to do so will likely result in issues at run time, such as resource leaks, deadlock, etc.

The Tour of Go is not meant as an exhaustive introduction to Golang; it does a great job at demonstrating how defer works when the enclosing function returns normally, but it leaves some important subtleties out.

How well do you understand defer?

Quiz time!

To test Gophers’ understanding of defer’s semantics, I ran the following poll on Twitter:

Functions foo and bar have exactly the same behaviour/semantics:

func foo(f func()) {
    defer fmt.Println("bye")
    f()
}

func bar(f func()) {
    f()
    fmt.Println("bye")
}

True or false?

Take a moment to think about it… What would your answer have been?

Although the poll’s response rate is low (only 16 respondents) and the respondents’ level of proficiency with Golang difficult to ascertain, I do find the results revealing: the majority of respondents (56%) answered “true”, yet the correct answer is “false”.

An explanation

Functions foo and bar do not have the same semantics. In particular, if function f panics when invoked—either because it happens to be nil or because a panic occurs during its execution—function bar will simply panic (without printing anything), whereas function foo will print “bye” to standard output before panicking. You can try it for yourself on the Go Playground.

In this case, if fmt.println("bye") is to be executed unconditionally, using defer is not optional, because function bar has no guarantee that its callers will pass a function argument that doesn’t panic when invoked. Thanks to the guarantees against panic that defer provides, function foo obviates the problem altogether.

To defer or not to defer: that is the question

This example should serve as a caution. defer is easily misconstrued (guilty!) as a mere syntactic convenience. A failure to use it may result in serious issues in your Go programs.

Granted, if you’re writing performance-critical code (such as a database) and if you know what you’re doing, you may be driven to eschew deferred statements in some places of your code, but

Always use defer (unless you have a good reason not to).

is a good rule of thumb. To paraphrase Bill Kennedy’s (goinggodotnet on Twitter) exhortation, which he repeats liberally in his video course:

Correctness takes precedence over performance.

Having a firm grasp of defer’s semantics is likely to pay great dividends towards the correctness of your Go programs. Don’t defer reading the fine print about defer :)

Additional resources