For Go learners: What is that damn defer supposed to mean everywhere in Go code?
If you’re a Go newbie like me and you have to jump in at the deep end and read “real” Go code, you’re constantly surprised by language features you don’t know yet. Fortunately, they’ve always been nice surprises so far. One of the Go features that surprised me is introduced by the keyword defer
, which can precede a function call.
My Go mentor roughly explained to me that defer
causes the following function not to be called immediately, but at a later time. And he told me to have a closer look at this language feature. Of course I don’t need to be told twice.
There is a GitHub repo for this article. Where it made sense, I included some sample code for you to try out.
Explained in a nutshell: A Tour of Go
I find my first starting point on this topic in Go’s introductory tutorial A Tour of Go. (According to my Go mentor, I should also take a look at this urgently. Next mission accomplished. At least at points √)
It says:
“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.”
The accompanying sample code reads:
Code example 1:
|
|
The call to the function in line 6 is delayed (note: as well “to defer”) until the end of the main() function. When executing the function, “hello” is printed first, followed by “world”, even though the order of the statements is reversed. Ok. Understood. √
Understood defer?
But somehow I have a feeling that there is more to know on the subject. For example: Why would you want a function to be executed later than in the original code sequence? And what are typical situations where a defer
could help me?
Detailed explanation in the book “The Go Programming Language”.
Next I turn to the Go textbook “The Go Programming Language”. Chapter 5.8. is called Deferred Function Calls. As a code example, a function title(url string)
is listed here. The function takes a URL and extracts the title from the HTML of the corresponding web page.
Code example 2:
|
|
Original listing on Github: https://github.com/adonovan/gopl.io/blob/master/ch5/title1/title.go
Important background info: opened resources must be closed again.
We can see in the first code line that http.Get(url)
is used to retrieve a URL. Later (in lines 10 and 15) the response body is closed with resp.Body.Close()
. This is explained twice in the documentation of the package http:
- “The client must close the response body when finished with it”
- “Caller should close resp.Body when done reading from it.”
A network connection was opened in the background to allow the HTTP call to be made. The garbage collector of Go, however, does not care about freeing resources that are no longer needed, that have been made available by the operating system. This includes open network connections as well as open files. That means, closing network connections (and also open files) is the responsibility of the programmer. (Source: “The Go Programming Language” on page 125). This is also relevant in the event of an error.
Double the work: resp.Body.Close() is called multiple times
In the current state, the program contains an awkward code repetition: the part resp.Body.Close ()
has to be called twice to close the open network connection in case of an error as well as in the normal program flow. If another case of error must be handled in the program logic the network connection must also be closed a third time in the newly created program branch. It would be also possible that the function contains clearly more code and it becomes difficult to keep an eye on all parts of the code, at which the closing of the network connection is necessary. The longer the function becomes, the more difficult it becomes to keep track of all these places. All in all it is clear: This kind of code repetitions could easily be forgotten or are not easy to maintain. This is a problem. With defer
Go offers a means against such kinds of repetitions. This answers my question in which situations defer
can be useful. √
Defer solves the “cleanup problem” of two-part operations
Typically, the defer statement is used in situations where operations are two-part e.g. the opening and closing of files or the establishment and later termination of a network connection. This is to ensure that resources, to which a first access has taken place, that these ressources get a second access at a later time, which neutralizes the first access. ‘defer’ always takes care of the second access, which - in my opinion - always has a cleanup character.
Where does a defer belong?
The proper place for a defer
statement is immediately after a resource comes into play or is opened without errors. In the case of http.Get(url)
from the example above, the defer
statement should be right after the error handling of the GET call, to properly close the network connection again after the surrounding function has finished. Thus, the revised variant of the title function from above reads:
|
|
Detailed program here: https://github.com/adonovan/gopl.io/blob/master/ch5/title2/title.go
The line with the defer
is executed after the return statement. This ensures that the network connection is closed as the very last step in any case.
The book also contains other uses for defer
, but I will skip them in this article. You don’t have to understand everything down to the last detail from the beginning.
Additional info in the Go blog: Processing in LIFO order
When functions get bigger, there can be problems closing open files.
The defer statement ensures that as the very last step each opened file is closed - no matter what happens in between. The call can be written immediately after the file has been opened. The closing action is executed at the very end - even if it is written much earlier. It goes into the list of actions to be executed only as a closing.
Finally, I look at what the Go blog says about defer. There I learn an interesting detail about exactly how it works. The defer
statement pushes the following function call into a list and collects there in incoming order other function calls, which were also marked with defer
. The processing of this list happens - as we already know now - after the surrounding parent function has been terminated with return. Now there is only the question of the order of the processing.
For a change, here is an example I made up myself with horses (but not only for horse enthusiasts):
|
|
Link on GitHub: https://github.com/ChristianBartl/gs9-article-about-defer-in-go/blob/main/lifo/ horserace.go
The output of the program looks like this:
|
|
We see, in the first method, the horses are listed in the same order as they are in the slice.
In the second method, the order is exactly reversed: the last horse is listed first. The line with defer
causes the first loop pass with “Winner” to be stored in a stack of is stored. Next in the stack is Dark horse, followed by Mediocre horse and Lame duck.
When processing this stack, the element inserted last in the stack is processed first, i.e. Lame duck. Therefore, Lame duck is output first this time. After further processing of the stack, this time we have the racehorses listed in reverse order.
So Last In First Out: If in a function more than one call is marked with defer
then the order at the end LIFO is processed. √
Summary
So let’s summarize:
-
When executing func main() { defer fmt.Println(“world”) fmt.Println(“hello”)}, due to defer` will print “hello” first and “world” afterwards although the order of the statements is is exactly reversed.
-
With
defer
Go offers a means to not forget annoying code repetitions, e.g. in order to files or network connections in all places. -
defer
makes sure that every opened file is closed again - no matter what happens in between. And only as the very last step the network connection. (Can still be written directly after opening). -
Principle Last In First Out: If in a function several calls are marked with
defer
, then the sequence is processed LIFO at the end.
Useful for me when getting started with Go were: A Tour of Go, the book “The Go Programming Language” (https://www.gopl.io/), and as companion reading the Go Blog.
And here’s the repo to go with my article again.
Did you make it this far? Yayy! As we all know, there’s another extra in the credits:
Our own code: for employees only
Where in the custom code did this occur?
It was about gRPC connections:
|
|
I can’t link the exact code here (it’s top secret!), but if you join our Go team, you can have a look at it! -> [“Go” ;-) to job-offer Go (Golang) Software Developer (*/d/f/m)](https://gs-9.com/jobs/ go-golang-software-engineer/)
Crazy world // Whimsical application example
To make the reader’s head spin a bit at the end, here’s the hint: The software we use to manage this article and our entire blog is of course written in Go and is called Hugo
https://github.com/gohugoio/hugo.
Since files are opened, read and processed there, Hugo naturally also uses defer
at one point or another. So in summary: the article about Go’s defer
runs on Go software that uses defer
to close opened files.
I have checked this, it is really true. The proof can be found here: hugo/scripts/fork_go_templates/main.go / and also in all other uses of defer in hugo
Now it’s your turn
What is your experience with Go? What helped you to get started? Do you use defer
(often)?
Let us know, you are always welcome in our IRC-channel #geekspace9! you are always welcome! Also via email and [Twitter](https:// twitter.com/geekspace9) we are looking forward to hearing from you. And of course, even more about your application for the Go-Team, which is looking for new members.