Sometimes we have to postpone things, like that trip to Hawaii ๐Ÿ˜Ž ๐Ÿ–๏ธ or buying the tickets to that big game ๐ŸŸ๏ธ or cleaning up the house ๐Ÿงน or waiting for a ๐Ÿ’ฒBig Sale๐Ÿ’ฒ on our favorite items. This is what’s known as deferring. You can also defer to someone else for their advice, like if someone asks ๐Ÿ—จ๏ธ “Why’s the sky blue?” – I would say ๐Ÿ’ฌ “Uhhh ๐Ÿ˜…, I defer to my high school science teacher.”

The same thing can be done in Go. We can defer function calls to be executed. Why is this useful? ๐Ÿค” There are two main driving reasons I can see ๐Ÿ‘๏ธ as to why we use defer. Readability and control flow. In Go it’s idiomatic (common) to see code that tends towards the return early pattern . A fellow Gopher, Mat Ryer also talks about it as line of sight … It’s almost wrong to nest clauses in Go and reading that code I’d raise an eyebrow of concern ๐Ÿคจ

The thing is, if we aren’t afraid to return early in Go, we must have a way of making sure that the resources that our functions grab can be released at every single point of return โ—โ— What’s that in the sky? It’s a bird ๐Ÿฆ It’s a plane โœˆ๏ธ It’s defer ๐Ÿฆธ

The functional part of defer comes with it’s ability to run a function no matter where we exit from the function. The other added benefit is the code that will get a resource can let go of it on the next line. This makes readability and glanceability much higher and makes catching ๐Ÿ•ธ๏ธ๐Ÿชฐ bugs much easier because you’re not managing where you have to let that resource go.

Setup

Let’s make our directory defers and the files we want inside of that directory example_test.go defers.go

mkdir defers
touch defers/example_test.go defers/defers.go

Now let’s open up defers.go and for the very first line we’ll add

package defers

Next for example_test.go for the very first line we’ll add

package defers_test

We can import basics/defers into cmd/main.go and run functions from there with go run cmd/main.go and also to run our example_test.go ๐Ÿ‘ we use go test defers/example_test.go in the commandline.

Where’s it Start ๐Ÿ™‚ Where’s it End ๐Ÿ™ƒ

A defer statement can be anywhere in a function and it will always be the last statement ran before the return. It is typical to see defer after some resource has been acquired so that it can be released at the end of the functions lifetime ๐ŸŒˆ

From the late great Ricky Bobby ๐ŸŽ๏ธ

If you ain’t first you’re last.

defer takes that to heart ๐Ÿ’œ and is always the last line to be executed before the return. It’s important to note that it’s before the return statement is executed.

Coding Time!

https://twitter.com/egonelbre

defers.go

// RunAtEnd shows that using the `defer` keyword will make that function
// run at the very end of our function.
func RunAtEnd() {
	defer fmt.Println("defer: this is the first line, but the last output.")
	fmt.Println("This is the second line, but first to be printed.")
	fmt.Println("A couple of more lines for good measure.")
	fmt.Println("Never hurt anyone.")
}

example_test.go

func ExampleRunAtEnd() {
	defers.RunAtEnd()
	// Output:
	// This is the second line, but first to be printed.
	// A couple of more lines for good measure.
	// Never hurt anyone.
	// defer: this is the first line, but the last output.
}

Last In, First Out ๐Ÿ“š (LIFO)

What happens if we have multiple defer statements? ๐Ÿค” If you’re familiar with a stack it works the same. If you’re not – defer statements work like a stack of books ๐Ÿ“š If you want to get to the first defer (first book๐Ÿ“˜) you have to remove the top two ๐Ÿ“•๐Ÿ“— and that would be the same as executing them. So to get to defer 1 we have to execute defer 3 and then defer 2 – The same as the books we remove book 3๐Ÿ“• and then book 2๐Ÿ“— to get book 1 ๐Ÿ“˜

Coding Time!

https://twitter.com/egonelbre

defers.go

// LIFO shows that if using multiple defers they work in a Last In First
// Out (LIFO) fashion.
func LIFO() {
	defer fmt.Println("First In. Third Out")
	fmt.Println("Random line 1")
	fmt.Println("Random line 2")
	defer fmt.Println("Second In. Second Out")
	fmt.Println("Random line 3")
	defer fmt.Println("Third In. First Out")
}

example_test.go

func ExampleLIFO() {
	defers.LIFO()
	// Output:
	// Random line 1
	// Random line 2
	// Random line 3
	// Third In. First Out
	// Second In. Second Out
	// First In. Third Out
}

Argument’s Evaluated

I’ll be honest โœ‹ this doesn’t seem intuitive and an argument ๐Ÿ˜น can be made for either side. It’s little endian versus big endian all over again ๐Ÿ˜ฑ The thing is we have an answer in this case. Here’s the problem, we have a defer statement with a small function, f, we want ran at the end of our big function , F, but we pass in an argument to that small function f(arg). When is that small function f(arg) executed with that argument? At the end of the big function F or where you declare it…? Let’s find out ๐Ÿ˜

Coding Time!

https://twitter.com/egonelbre

defers.go

// ArgumentsEvaluated shows the arguments passed to a `defer`'s function
// are immediately evaluated and not evaluated at the end of the function's
// lifetime
func ArgumentsEvaluated() {
	whatWillIBe := 42
	defer fmt.Println("Number in defer:", whatWillIBe)
	whatWillIBe = 9001
	fmt.Println("Number at end of function:", whatWillIBe)
}

example_test.go

func ExampleArgumentsEvaluated() {
	defers.ArgumentsEvaluated()
	// Output:
	// Number at end of function: 9001
	// Number in defer: 42
}

Sneaky Named Returns

Another sneaky thing that can happen with defer is which values you pass to it may give you what feels like unexpected behavior. This is a good time to not dig too deep into it and just have knowledge of it… Else you’ll find your head spinning ๐Ÿ˜ตโ€๐Ÿ’ซ

Though if you’re curious, this has to do with what scope defer works on. Many languages work around what’s known as the block scope, but defer works on the functional scope. A function can have named returns as we learned in the lesson on functions which puts that named parameter on the function’s scope, not the inside block’s scope, therefore it won’t be able to push that to function level… Confusing? Yeah, I told you ๐Ÿ˜ต

Coding Time!

https://twitter.com/egonelbre

defers.go

// NamedReturn shows that before we return a named value, if we change it
// in a deferred function, it will change it before returning the value.
// Compare with Return.
func NamedReturn() (gi string) {
	gi = "Golden Idol"
	defer indianaJones(&gi)
	return gi
}

// Return shows that depending on the scope, in the function or out of the
// function, the return value will change. Compare with NamedReturn.
func Return() string {
	gi := "Golden Idol"
	defer indianaJones(&gi)
	return gi
}

func indianaJones(s *string) { *s = "Bag of sand" }

example_test.go

func ExampleNamedReturn() {
	fmt.Println(defers.NamedReturn())
	// Output: Bag of sand
}

func ExampleReturn() {
	fmt.Println(defers.Return())
	// Output: Golden Idol
}

Panic! At the ๐Ÿชฉ Function…๐Ÿ˜น

Here is where we see our first very necessary use of defer. In Go, there are no exceptions like you might find in other languages. Situations that produce errors are handled differently in Go as we found out in the previous lesson . However, there is a difference between a bad situation ๐Ÿ˜ฎ and a catastrophic ๐ŸŒŠ ๐ŸŒ‹ ๐ŸŒช๏ธ ๐ŸŒฉ๏ธsituation.

This distinction in Go is made with panic, but we’ll get into that more in the next lesson . Just know in order to recover from such a situation, we need to recover before the function ends, but in order to recover we must defer.

After this point we’ve covered all we need to know about defer so feel free to head over to the next lesson But if you come from a language that doesn’t have a defer or if you’re new to it all, the next topic has lots of examples of where we use defer in Go.

Coding Time!

https://twitter.com/egonelbre

defers.go

// RecoverPanic shows how to recover from a panic by using a `defer`
// statement and the `recover` keyword that will be covered in the next lesson.
func RecoverPanic(calmdown bool) {
	if calmdown {
		defer recuperate()
	}
	panics()
	fmt.Println("Looks like we recovered in time.")
}

func panics() {
	panic("WE'RE ALL GOING DOWN, THIS IS THE END!!!")
}

func recuperate() {
	if r := recover(); r != nil {
		fmt.Println("Recovered from:", r)
	}
}

example_test.go

func ExampleRecoverPanic() {
	// NOTE(jay): Uncomment this if you're a brave soul ๐Ÿซฃ
	// defers.RecoverPanic(false)
	defers.RecoverPanic(true)
	// Output: Recovered from: WE'RE ALL GOING DOWN, THIS IS THE END!!!
}

Out in the Wild ๐Ÿฏ (Useful places to use defer)

I admit that defer may be completely new to the uninitiated, veterans, and experts alike, depending on which language you come from, if any. So I’ve put together a small example set of where defer is absolutely necessary to call.

โš ๏ธโš ๏ธ WARNING โš ๏ธโš ๏ธ None of these examples will be thoroughly explained as I do the rest of our code. Each example deserves it’s own lesson entirely. This is meant more as a sample set to open your mind ๐Ÿคฏ to the possibilities of defer.

Coding Time!

https://twitter.com/egonelbre

Closing A File

defers.go
// FileClose shows that we **always close** file descriptors after we open
// them.
func FileClose() {
	f, err := os.OpenFile("example.txt", os.O_RDWR|os.O_CREATE, 0744)
	if err != nil {
		fmt.Println(err)
	}
	defer closeFile(f)

	_, err = f.WriteString("๐Ÿ‘‹ ๐ŸŒ")
	if err != nil {
		fmt.Println(err)
	}
}

func closeFile(f *os.File) {
	fmt.Println("closing")
	if err := f.Close(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}
}
example_test.go
func ExampleFileClose() {
	defers.FileClose()
	f, err := os.Open("example.txt")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Print("Reading contents of example.txt: ")
	io.Copy(os.Stdout, f)
	// Output:
	// closing
	// Reading contents of example.txt: ๐Ÿ‘‹ ๐ŸŒ
}

Removing Temporary Files

defers.go
// TempFileRemoveClose shows that with a temp file (and temp directories)
// we should clean up after ourselves by removing the file and closing the file
// descriptor.... **Always remove and close** temp file descriptors.
func TempFileRemoveClose() {
	f, err := os.CreateTemp(".", "tempfile")
	if err != nil {
		fmt.Println(err)
	}
	defer removeFile(f)
	defer closeFile(f)
}

func removeFile(f *os.File) {
	fmt.Println("removing")
	if err := os.Remove(f.Name()); err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}
}
example_test.go
func ExampleTempFileRemoveClose() {
	defers.TempFileRemoveClose()
	// Output:
	// closing
	// removing
}

Closing HTTP Response Bodies

defers.go
// HTTPBodyClose shows that we **always close** our `*http.Response` body
// after it successfully completes.
func HTTPBodyClose() {
	res, err := http.Get("https://gophergo.dev")
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}
	defer res.Body.Close()

	buf := make([]byte, 4096)
	io.ReadFull(res.Body, buf)
	fmt.Printf("Does the first 4KiB of data have Gopher Go in it? %t\n",
		bytes.Contains(buf, []byte("Gopher Go")))
}
example_test.go
func ExampleHTTPBodyClose() {
	defers.HTTPBodyClose()
	// Output: Does the first 4KiB of data have Gopher Go in it? true
}

Cancel Context

defers.go
// CancelContext shows a contrived way of canceling a context. Though it
// is a toy example, it is still very important that you **always cancel** your
// context after you're done using it.
func CancelContext(d time.Duration) {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
	defer cancel()
	t := time.NewTimer(d)
	select {
	case <-ctx.Done():
		fmt.Println("You're too slow!!!")
	case <-t.C:
		fmt.Println("Don't forget to cancel!")
	}
}
example_test.go
func ExampleCancelContext() {
	defers.CancelContext(300 * time.Millisecond)
	defers.CancelContext(5 * time.Millisecond)
	// Output:
	// You're too slow!!!
	// Don't forget to cancel!
}

Mutex Locks

defers.go
// account acts as a bank account with a balance
type account struct {
	balance int64
	sync.Mutex
}

// NewAccount ...
func NewAccount(balance int64) *account { return &account{balance: balance} }

// Balance gets the amount of money stored in cents. It locks the account from
// other transactions, such as a deposit, so the statement is accurate.
func (a *account) Balance() int64 {
	a.Lock()
	defer a.Unlock()

	return a.balance
}

// Deposit places money into the account. It locks the account so that other
// transactions, such as Withdrawal, cannot effect the balance while it's being
// updated.
func (a *account) Deposit(amount int64) (int64, error) {
	if amount < 0 {
		return -1, errors.New("Cannot deposit a negative amount.")
	}
	a.Lock()
	defer a.Unlock()

	a.balance += amount
	return a.balance, nil
}
example_test.go
func ExampleNewAccount() {
	a := defers.NewAccount(1000)
	a.Deposit(100)
	fmt.Println("Balance is:", a.Balance())
	// Output: Balance is: 1100
}

Source File ๐Ÿ“„

The Source File

Test File ๐Ÿ“

The Test File