Home

Blog

Contact

Resume for Steven E. Newton

Papers

Readings for Code Janitors

Services


Steven E. Newton
Crater Moon Development
© 2003-2023

Go Defer Tricks

1 Jan 2023 573 words. (3 min)

Defer Statements

Go’s defer is a bit like the C++ idiom known as Resource Acquisition is Initialization, or RAII. This is a slightly opaque term for ensuring, through language mechanisms, the cleanup and release of any resources acquired by the program, such as locks, file descriptors, and network connections, at the end of the block where they are used. The defer statement is used to ensure the execution of some code when the function exits, regardless of whether or not the exit was normal or early.

"A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking."

The Go Programming Language Specification, Defer statements

The defer executes when the function exits, not at the end of a block. Unlike variable scoping, which is tied to blocks, defer is tied to function scope.

A common Go idiom is to defer closing a file (or file-like object) to ensure the descriptor is closed regardless of where or how the function exits. Often seen in HTTP interactions.

resp, err := http.Get("http://example.com/")
if err != nil {
     // handle error
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)

Always check for and handle any errors before using defer. The following code will panic:

        client := &http.Client{}
        req := &http.Request{}
        res, err := client.Do(req)
        defer res.Body.Close()
        if err != nil {
                log.Fatal(err)
        }

The variable res will be nil when defer runs.

Be careful using defer to close writeable files. If the close function returns an error, it should be handled.

As the Tour of Go notes,

"The deferred call’s arguments are evaluated immediately, but the function call is not executed until the surrounding function returns."

A Tour of Go: defer

Bookending functions in logs. The deferred logging will always print the value of i as passed to the function, regardless of how i changes later.

func SomeOtherFunc() (string, error) {
        if rand.Intn(1) == 0 {
                return "", errors.New("emit macho dwarf: elf header corrupted")
        } else {
                return "OK", nil
        }
}
func SomeFunc(i int) error {
        log.Printf("=====> function %d entry", i)
        defer log.Printf("<===== function %d exit", i) // will always get called

        result, err := SomeOtherFunc()
        if err != nil {
                return err
        }
        i++
        // do something with result
        fmt.Println(result)
        return nil
}

Note the language’s use of stack for calls to defer, so on function exit, they calls are executed last-in-first-out.

func Countdown() {
        defer fmt.Println("Liftoff...")
        defer fmt.Println(1)
        defer fmt.Println(2)
        defer fmt.Println(3)
}

prints

3
2
1
Liftoff...

Timer

 1func TimeIn(name string) func() {
 2        in := time.Now()
 3        return func() {
 4                d := time.Now().Sub(in)
 5                log.Printf("%s took %s", name, d)
 6        }
 7}
 8func SomeFunc() {
 9        timeOut := TimeIn("SomeFunc")
10        defer timeOut()
11        // body of SomeFunc
12}

An example of the timer defer in action is common in prometheus instrumentation

func TimeMe() {
    timer := NewTimer(myHistogram)
    defer timer.ObserveDuration()
    // Do actual work.
}

In there case where deallocating and cleaning up requires more than one step, defer can take an anonymous function. For example

defer func() {
        cleanupOne()
        cleanupTwo()
}()

This can also be done with multiple defer statements, but if you need the cleanup to happen in an order different from the defer calls, this is solution.

More from Mat Ryer’s “Idiomatic Go Tricks” talk at Golang UK Conference 2016.

Writing as a Programmer ->