Loading...
Loading...
Marco Franssen

In my previous post, I covered setting up a Go development environment and writing a simple Hello World program. If this is your first Go project, read that post first, then come back to learn how to use defer.
Deferred calls run when a function exits, including when a panic unwinds the call stack. A panic interrupts normal execution; if it isn't recovered, the program stops. Don't use panic for routine error handling—it's better to handle errors using Go's error values. I'll use panic here to demonstrate defer, but please don't take that as a best practice.
Let's take a look at an example:
package main
import "fmt"
func main() {
deferExample()
fmt.Println("Returned from deferExample.")
}
func deferExample() {
defer fmt.Println("Deferred log.")
fmt.Println("Print line.")
}The code above produces the following output.
go run main.go
Print line.
Deferred log.
Returned from deferExample.As you can see, the deferred call runs at the end of deferExample, just before the function returns. Now let's look at an example that panics. Add the following functions to main.go and call deferPanicExample from your main function.
func deferPanicExample() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recover from panic", r)
}
}()
fmt.Println("Call recurse.")
recursePanic(0)
fmt.Println("Return normally from recurse.")
}
func recursePanic(count int) {
if count > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", count))
}
defer fmt.Println("Defer log in recurse", count)
fmt.Println("Print line in recurse", count)
recursePanic(count + 1)
}The code above defines a recursive function that panics as soon as count is greater than 3. fmt.Sprintf formats a string from a value; see the fmt documentation for more information about formatting verbs such as %v. Here, we use it to pass the count to panic as a string.
Another interesting part is recover(), which lets us recover from a panic further down the call stack. We call it inside a deferred anonymous function in deferPanicExample, so it runs when that function exits. Let's look at the program's new output.
go run main.go
Print line.
Deferred log.
Returned from deferExample.
Call recurse.
Print line in recurse 0
Print line in recurse 1
Print line in recurse 2
Print line in recurse 3
Panicking!
Defer log in recurse 3
Defer log in recurse 2
Defer log in recurse 1
Defer log in recurse 0
Recover from panic 4
Returned from deferPanicExample.Notice that the Println with the message Return normally from recurse. never runs: the panic interrupts execution before it reaches that line. The deferred calls in the recursive functions run as the call stack unwinds, in reverse order. This follows a LIFO (last in, first out) pattern. Finally, the deferred anonymous function runs and recovers from the panic. Since we passed the count to panic, the recovery log includes its value at the moment the program panicked.
Now let's rewrite the recursive function to return normally instead of panicking, and compare the flow.
func deferRecurseExample() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recover from panic", r)
}
}()
fmt.Println("Call recurse.")
recursePanic(0)
fmt.Println("Return normally from recurse.")
}
func recurse(count int) {
if count > 3 {
fmt.Println("Stopping recursion!")
return
}
defer fmt.Println("Defer log in recurse", count)
fmt.Println("Print line in recurse", count)
recurse(count + 1)
}In the normal-return example, Return normally from recurse. is printed. The recover() call returns nil because there's no panic to recover from, so it doesn't print the recovery message.
go run main.go
Print line.
Deferred log.
Returned from deferExample.
Call recurse.
Print line in recurse 0
Print line in recurse 1
Print line in recurse 2
Print line in recurse 3
Panicking!
Defer log in recurse 3
Defer log in recurse 2
Defer log in recurse 1
Defer log in recurse 0
Recover from panic 4
Returned from deferPanicExample.
Call recurse.
Print line in recurse 0
Print line in recurse 1
Print line in recurse 2
Print line in recurse 3
Stopping recursion!
Defer log in recurse 3
Defer log in recurse 2
Defer log in recurse 1
Defer log in recurse 0
Return normally from recurse.
Returned from deferRecurseExample.Now that we've seen how defer works, let's look at a more practical use case.
Depending on your background, you might know the Closeable interface in Java or IDisposable in C#. Classes such as database connections and file readers implement these interfaces so you can release resources by calling close() or dispose(). Failing to release those resources can cause leaks. In C#, you can use a try finally block or the shorthand using statement to ensure cleanup runs even if an exception occurs.
In Go, we can use defer to run cleanup when the function exits. Let's look at a small example that reads a specified number of bytes from a file.
package main
import (
"fmt"
"os"
)
func main() {
readBytes(10)
}
func readBytes(amount int) {
pwd, _ := os.Getwd()
f, _ := os.Open(pwd + "/awesome.txt")
defer fmt.Println("File closed")
defer f.Close()
defer fmt.Println("Going to close file")
bytes := make([]byte, amount)
count, _ := f.Read(bytes)
fmt.Printf("%d bytes: '%s'\n", count, string(bytes))
}This program reads up to 10 bytes from our text file and prints them as a string. For more about string formatting verbs, see the fmt package documentation.
echo "I am super sexy." > awesome.txt
go run main.go
10 bytes: I am super
Going to close file
File closedAs you can see, I create a file in the same folder containing I am super sexy.. I don't want everyone to know I'm sexy ;-), so now you know why the program reads only the first 10 bytes. Sharing I am super with the world should be enough.
Once the file is open and the close call is deferred, the file handle will be closed when the function exits, including if a later operation fails. Also note that multiple defer statements in the same function run in the reverse order in which they're registered.
Thanks for reading! Have a look at this post on golang.org for more details on defer, panic, and recover in Go.
Marco Franssen
Learn Go test and benchmark conventions with a JSON example, then explore code coverage, Testify assertions and options for comparing performance.
Marco Franssen
Explore Go concurrency with practical goroutine and channel examples, including buffered channels, GOMAXPROCS, and the difference between concurrency and parallelism.
Marco Franssen
Set up your first Go project on Windows, learn basic syntax, write and run tests, and cross-compile a simple application for Linux and Raspberry Pi.
Marco Franssen
Find your Ethernet driver, add it to a Windows 10 PE image with DISM, and start Windows setup from a network share on a laptop without an optical drive.