Go · Defer · Wut

Defer

Go has a (sometimes) handy feature called defer. A list of commands is executed when a function exits, the defer list. This makes it simpler for a developer to place near relevant code some other code that needs to be executed as the function exits.

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Starting")
	defer fmt.Println("Deferred")
	fmt.Println("Ending")
}

// Output
Starting
Ending
Deferred

The most common usecase for defers is ensuring references to external resources are closed when the function exits, network, or file handles, opened within the function normally have a deferred close call nearby, so maintainers can see, very quickly, that the cleanup for that resource does indeed happen. rather than having to look through the function for the cleanup routines.

The defer call locks in the value it is given when it is called, so that, upon execution, even if the value has changed, defer uses the value it was given.

func Closing(s string) {
    fmt.Println(s)
}

func main() {
        a := "red"
	defer Closing(a)
        a = "blue"
	defer Closing(a)
}

// Output
blue
red

Keeping in mind that the defers are loaded into a list, and the list is executed from the end backwards, like a stack, which means that the last added is the first executed.

Be aware, though, that when the defer is passed a pointer, the function it is calling is going to dereference the pointer it has been passed, and will get the value held at that address.

func Closing(s *string) {
    fmt.Println(*s)
}

func main() {
        a := "red"
	defer Closing(&a)
        a = "blue"
	defer Closing(&a)
}

// Output
blue
blue

This behaviour is dependant on how the pointer is being handled (code supplied by fizzie on IRC)

type foo struct { i int }

func newFoo(i int) *foo {
	return &foo{i}
}

func (f *foo) Close() {
	fmt.Println(f.i)
}

func main() {
	p := &foo{123}
	defer p.Close()
	
	p = &foo{456}
	defer p.Close()
}

// Output
456
123

Now for something really scary. (Keep in mind that Go’s syntactic sugar for calling the method on the pointer type obscures the fact that the actual call is (&f).Method, (code supplied by fizzie on IRC)

type foo int

func (f foo) noPointer() {
	fmt.Printf("noPointer: %d\n", f)
}

func (f *foo) yesPointer() {
	fmt.Printf("yesPointer: %d\n", *f)
}

func main() {
	f := foo(123)
	defer f.noPointer()
	defer f.yesPointer()
	
	f = foo(456)
	defer f.noPointer()
	defer f.yesPointer()
}

// Output
yesPointer: 456
noPointer: 456
yesPointer: 456
noPointer: 123

The yesPointer method is using the address of foo. When the deferred call is executed it is dereferencing the foo pointer, and using the value found there. The scary thing about this is, if the foo type existing in another package, then the user (writer of the caller code) may not be aware of the subtle differences in behaviour, it’s not clear that they are using the method on the pointer type.

Published:
comments powered by Disqus