3 minutes
Defer: sweet, but no syntactic sugar
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
andbar
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 ¶
- Defer statements (language specification)
- Defer, panic, and recover (Go Blog)