Defer in Go
Table of Contents
The defer keyword is a fairly unique feature of Go. It allows a function to be executed automatically just before its enclosing function returns. The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.
All the content from our Boot.dev courses are available for free here on the blog. This one is from the "Functions" chapter of Learn Go. If you want to try the far more immersive version of the course, do check it out!
Deferred functions are typically used to clean up resources that are no longer being used. Often to close database connections, file handlers and the like.
Simple Defer Example - Hello World
func main() {
defer fmt.Println("world") // deferred until main() exits
fmt.Println("hello")
}
// prints:
// hello
// world
When Would You Want to Defer Something?
After programming in Go, it's really hard to remember how I dealt with closing connections or files in other languages. The defer statement is a very clean way to deal with any sort of "cleanup" that needs to happen in a function.
For example:
func GetUsername(dstName, srcName string) (username string, err error) {
// Open a connection to a database
conn, _ := db.Open(srcName)
// Close the connection *anywhere* the GetUsername function returns
defer conn.Close()
username, err = db.FetchUser()
if err != nil {
// The defer statement is auto-executed if we return here
return "", err
}
// The defer statement is auto-executed if we return here
return username, nil
}
In the above example, the conn.Close() function is not called here:
defer conn.Close()
It's called:
// here
return "", err
// or here
return username, nil
Depending on whether the FetchUser function errored.
Defer is a great way to make sure that something happens before a function exits, even if there are multiple return statements, a common occurrence in Go.
A Real World Example - Closing an HTTP Response Body
resp, err := http.Get(url)
if err != nil{
log.Println(err)
}
defer resp.Body.Close()
In Go's standard http library, the documentation points out that HTTP responses must be closed by the client when it's finished. The client must close the response body when finished with it.
In the example above, you might be thinking, "I'll just close the response when I'm done with it, why should I defer it?". In my experience, the main reason to use defer is due to Go developers' liberal use of guard clauses. When a function has many exit points (places where it can return early), you don't want to prefix every return with a response closure. What if you miss one? Let's look at an example.
func getUser() (User, error) {
resp, err := http.Get("https://example.tld/users")
if err != nil{
return User{}, err
}
dat, err := io.ReadAll(resp.Body)
if err != nil {
resp.Body.Close()
return User{}, err
}
user := User{}
err = json.Unmarshal(dat, &user)
resp.Body.Close()
return user, err
}
Notice how resp.Body.Close() needs to be called in two places - at each potential exit point. With defer, we can simplify our code.
func getUser() (User, error) {
resp, err := http.Get("https://example.tld/users")
if err != nil{
return User{}, err
}
defer resp.Body.Close()
dat, err := io.ReadAll(resp.Body)
if err != nil{
return User{}, err
}
user := User{}
err = json.Unmarshal(dat, &user)
return user, err
}
Multiple Defers
The location of a defer statement inside a function matters. The deferred call is registered at the point where defer is executed, and it will run when the function returns. If you have multiple defer statements in a single function, they are executed in last-in, first-out order (the last deferred call runs first).
For example, you'd want to close a file before trying to remove it:
func CreateTempFile() {
f, _ := os.Create("temp-42.txt")
defer os.Remove(f.Name()) // executed second
defer f.Close() // executed first
fmt.Fprintln(f, "How many roads must a man walk down?")
}
When Are Function Arguments Evaluated?
Unlike other higher-order functions in Go, when you "pass" a function to the defer keyword, you pass an entire function call, not just the name of the function. This allows the function's arguments to be evaluated immediately. The defer keyword just ensures that the body of the function won't run until the parent function returns.
func main() {
printMath(5, 6, multiply) // the "multiply" function is passed without arguments
}
// printMath does some math and prints the result
func printMath(x, y int, mathFunc func(int, int) int) {
fmt.Println(mathFunc(x, y))
}
func multiply(x, y int) int {
return x * y
}
The defer keyword on the other hand does take arguments.
defer fmt.Println(x + y)
x+y evaluates immediately, but doesn't print until main() exits.
Defer, Panic and Recover - Why You Shouldn't Do It
I don't want to spend too much time on this, but some people have stumbled across Go's built-in recover() function and thought it might be a good idea to use panic() and recover() like try and catch in other languages.
What Is the Recover() Function in Go?
Simply put, recover is a builtin function that regains control of a panicking goroutine. Recover is only used inside deferred functions. Calling recover() inside a deferred function stops the panicking sequence and retrieves the error message passed to the panic() function call.
func recoverWithMessage() {
if r := recover(); r!= nil {
fmt.Println("recovered from", r)
}
}
func fullName(firstName *string, lastName *string) string {
defer recoverWithMessage()
if firstName == nil {
panic("first name cannot be nil")
}
if lastName == nil {
panic("last name cannot be nil")
}
return fmt.Sprintf("%s %s\n", *firstName, *lastName)
}
func main() {
firstName := "Lane"
lastName := "Wagner"
fmt.Println(fullName(&firstName, &lastName))
fmt.Println(fullName(nil, nil))
}
// prints:
// Lane Wagner
// recovered from first name cannot be nil
The example above is a complicated and non-idiomatic way to handle runtime problems that would have been better dealt with by just passing error values. I understand that there are definitely edge-cases where use of panic() and recover() might make sense. That said, I've been writing Go professionally for about 5 years now and I've never felt a sufficient need, especially in application code. Do your best to refactor your project so you can just return errors like the good designers intended.
Frequently Asked Questions
What does defer do in Go?
Defer allows a function to be executed automatically just before its enclosing function returns.
When are arguments to a deferred function evaluated?
The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.
What are deferred functions typically used for?
Deferred functions are typically used to clean up resources, such as closing database connections and file handlers.
In what order do multiple deferred calls run?
Multiple defer statements are executed in last-in, first-out order, so the last deferred call runs first.
What is recover in Go?
Recover is a builtin function that regains control of a panicking goroutine. It is only used inside deferred functions.
