This page looks best with JavaScript enabled

Go defer

 ·  🎃 kr0m

Defer in Go is used to postpone the execution of an action until the function containing the defer statement finishes. This is especially useful to ensure that certain resources, such as files, connections, or locks, are properly released, regardless of how the function ends (whether normally or due to an error). Deferred functions are executed in LIFO (Last In, First Out) order, meaning the last declared defer is the first to execute when the function terminates.

This article is quite extensive, so I have organized it as follows:


If you are new to the world of Go, I recommend the following previous articles:


Basic Usage:

First, let’s look at an example where we open a file regularly without defer, where we see that if an error occurs, we must display an error message, close the file, and exit. If the file is successfully read, we must close the file and display the information.

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }

    fileInfo, err := file.Stat()
    if err != nil {
        fmt.Println("Error getting file info:", err)
        // Close file: Error
        file.Close()
        return
    }

    // Close file: Success
    file.Close()

    fmt.Println("File Name:", fileInfo.Name())
    fmt.Println("File Size:", fileInfo.Size(), "bytes")
    fmt.Println("File Mode:", fileInfo.Mode())
}
File Name: example.txt
File Size: 0 bytes
File Mode: -rw-r--r--

In the previous example, we can see that we had to make sure to close the file in all cases, both when the file is accessed correctly and when it is not. Having to control the file closure in various parts of the code can lead to programmer errors.

The best way to avoid this is to use defer right after successfully opening the file. With defer, we can ensure that the resource is released properly, regardless of how the function ends.

The final code would look like this.

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }

    // Defer the closure of the file
    defer file.Close()

    fileInfo, err := file.Stat()
    if err != nil {
        fmt.Println("Error getting file info:", err)
        return
    }

    fmt.Println("File Name:", fileInfo.Name())
    fmt.Println("File Size:", fileInfo.Size(), "bytes")
    fmt.Println("File Mode:", fileInfo.Mode())
}
File Name: example.txt
File Size: 0 bytes
File Mode: -rw-r--r--

Executing Anonymous Functions:

Defer statements are not limited to performing simple operations like closing a file; they can also execute anonymous functions.

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Start of main function")

    defer func() {
        fmt.Println("This is a deferred function")
    }()

    fmt.Println("End of main function")
}
Start of main function
End of main function
This is a deferred function

Executing Methods:

Another possibility offered by defer is to execute methods associated with structures , in this example we have the databaseConnection structure with two linked methods Open()/Close(). In the main() function, we create the object, open the database connection, and once we have successfully opened it, we declare a defer db.Close() to ensure that the connection is always closed, no matter what happens.

package main

import (
    "fmt"
)

type databaseConnection struct {
    connected bool
}

func (db *databaseConnection) Open() {
    db.connected = true
    fmt.Println("Database connection opened")
}

func (db *databaseConnection) Close() {
    db.connected = false
    fmt.Println("Database connection closed")
}

func main() {
    db := &databaseConnection{}
    db.Open()
    defer db.Close()
    fmt.Println("Performing database operations...")
}
Database connection opened
Performing database operations...
Database connection closed

Defer Stacking:

Deferred functions are executed in LIFO (Last In, First Out) order, meaning the last declared defer is the first to execute when the function terminates. In this example, we can easily see this behavior.

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Start of main function")

    defer fmt.Println("First deferred statement")
    defer fmt.Println("Second deferred statement")
    defer fmt.Println("Third deferred statement")

    fmt.Println("End of main function")
}
Start of main function
End of main function
Third deferred statement
Second deferred statement
First deferred statement
If you liked the article, you can treat me to a RedBull here