DEV Community

Lane Wagner for Boot.dev

Posted on • Originally published at qvault.io on

How to Properly Use Defer in Golang

hand signal

The post How to Properly Use Defer in Golang first appeared on Qvault.

What is the “defer” keyword in Go?

In the Go programming language, defer is a keyword that allows developers to delay the execution of a function until the current functions returns. What throws some people off is that the deferred function’s arguments are evaluated immediately, but the function itself doesn’t fire until the wrapping function exits.

Simple defer example – hello world

func main() {
    defer fmt.Println("world") // deferred until main() exits
    fmt.Println("hello")
 }

// prints:
// hello
// world
Enter fullscreen mode Exit fullscreen mode

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.

resp, err := http.Get(url)
if err != nil{
    log.Println(err)
}
defer resp.Body.Close()
Enter fullscreen mode Exit fullscreen mode

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 err
    }

    user := User{}
    err = json.Unmarshal(dat, &user)
    resp.Body.Close()
    return user, err
}
Enter fullscreen mode Exit fullscreen mode

Notice how resp.Body.Close() needs to be called in two places – at each potential exit point. With defer, we can simply 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 err
    }

    user := User{}
    err = json.Unmarshal(dat, &user)
    return user, err
}
Enter fullscreen mode Exit fullscreen mode

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 by 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
Enter fullscreen mode Exit fullscreen mode

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.

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
}
Enter fullscreen mode Exit fullscreen mode

The defer keyword on the other hand does take arguments.

defer fmt.Println(x + y)
Enter fullscreen mode Exit fullscreen mode

x+y evaluates immediately, but doesn’t print until main() exits.

What happens with multiple defer statements?

Deferred function calls are pushed onto a stack data structure. When the parent function returns, all its deferred calls are executed in the reverse order that they were created.

defer fmt.Println("third")
defer fmt.Println("second")
defer fmt.Println("first")

// prints:
// first
// second
// third
Enter fullscreen mode Exit fullscreen mode

Thanks for reading, now take a course!

Interested in a high-paying job in tech? Land interviews and pass them with flying colors after taking my hands-on coding courses.

Start coding now

Questions?

Follow and hit me up on Twitter @q_vault if you have any questions or comments. If I’ve made a mistake in the article be sure to let me know so I can get it corrected!

Subscribe to my newsletter for more coding articles delivered straight to your inbox.

Top comments (0)