SoFunction
Updated on 2025-03-04

Defer usage scenarios and precautions in Go

1. Introduction

defer executes the passed function before the current function returns. It is often used to close file descriptors, close database connections, and unlock resources.

Understanding this sentence mainly involves three aspects:

  • Current function
  • Execute before returning, of course the function may not return a value
  • The function passed in, that is, the defer key value is followed by a function, including ordinary functions such as (), or anonymous function func()

1.1 Use scenarios

The most common scenario for using defer is to complete some finishing work after the function call is finished, such as rolling back a database transaction in defer:

func createPost(db *) error {
    tx := ()
    // Used to roll back database events    defer ()
    
    if err := (&Post{Author: "Draveness"}).Error; err != nil {
        return err
    }
    
    return ().Error
}

When using database transactions, we can use the above code to call Rollback immediately after creating the transaction to ensure that the transaction will definitely rollback. Even if the transaction is really executed successfully, then calling () and then executing () will not affect the committed transaction.

1.2 Notes

usedeferThere are two common problems that will be encountered. Here we will introduce the specific scenarios and analyze the design principles behind these two phenomena:

The time of calling the defer keyword and how the execution order is determined when multiple calls to defer are called. The defer keyword is pre-calculated when passing parameters by passing values, resulting in the result that it does not meet the expected results.

Scope

The function passed to the defer keyword will run before the function returns.

Suppose we call the defer keyword multiple times in the for loop:

package main
 
import "fmt"
 
func main() {
	for i := 0; i < 5; i++ {
	    // FILO, first in and then out, the keyword defer that appears first will be pushed into the bottom of the stack, and will be finally removed and executed		defer (i)
	}
}
 

#run

$ go run

4

3

2

1

0

Running the above code will execute all expressions passing in the defer keyword in reverse order. Because (4) is passed in the last call to defer, this code will print 4 first. We can use the following simple example to enhance our understanding of the execution timing of defer:

package main
 
import "fmt"
 
func main() {
    // Code block	{
		defer ("defer runs")
		("block ends")
	}
 
	("main ends")
}

#Output

$ go run

block ends

main ends

defer runs

From the output of the above code, we will find that the function passed in defer is not executed when exiting the scope of the code block, it will only be called before the current function and method return.

Pre-calculated parameters

All function calls in Go are passed values.

Although defer is a keyword, it also inherits this feature. Suppose we want to calculate the time when the main function runs, we might write the following code:

package main
 
import (
	"fmt"
	"time"
)
 
func main() {
	startedAt := ()
	// I mistakenly think that startingAt will pass the argument to the function in the defer statement afterward	defer ((startedAt))
 
	()
}

#Output

$ go run

0s

The above code run results do not meet our expectations. What is the reason behind this phenomenon?

After analysis (or using debug method), we will find:

Calling the defer keyword will immediately copy the external parameters referenced in the function.

So the result of (startedAt) is not calculated before the main function exits, but is calculated when the defer keyword is called, which ultimately results in the above code outputting 0s.

The solution to this problem is very simple. We just need to pass anonymous function to the defer keyword:

package main
 
import (
	"fmt"
	"time"
)
 
func main() {
	startedAt := ()
    // Using anonymous functions, passing a pointer to the function	defer func() {
		((startedAt))
	}()
 
	()
}

#Output

$ go run

$ 1.0056135s

2. defer data structure

The corresponding data structure of the defer keyword in the Go language source code:

type _defer struct {
	siz       int32
	started   bool
	openDefer bool
	sp        uintptr
	pc        uintptr
	fn        *funcval
	_panic    *_panic
	link      *_defer
}

A brief introduction to several fields in the runtime._defer structure:

  • siz is the memory size of the parameters and results;
  • sp and pc represent stack pointers and caller program counters, respectively;
  • fn is a function passed in the defer keyword;
  • _panic is the structure that triggers the delayed call, which may be empty;
  • openDefer indicates whether the current defer has been optimized by open coding;

In addition to the above fields, runtime._defer also contains some fields used by the garbage collection mechanism. I will not explain too much here.

3. Execution mechanism

Heap allocation, stack allocation and open encoding are three ways to deal with the defer keyword.

  • Early Go languages ​​were allocated on the heap, but their performance was poor
  • Go language introduces structures allocated on the stack in 1.13, reducing the additional overhead by 30%.
  • In 1.14, an open encoding-based defer was introduced so that the additional overhead of the keyword is negligible

Don't make too many instructions on the heap allocation

3.1 Allocation on the stack

The defer keyword was optimized in 1.13. When the keyword is executed at most once in the function body, the structure will be assigned to the stack and called.

Apart from the allocation location, there is no essential difference between the allocation on-stack and the runtime._defer allocated on-heap, and this method can be applied to most scenarios. Compared with the runtime._defer allocated on-heap, this method can reduce the additional overhead of the defer keyword by ~30%.

3.2 Open coding

The defer keyword is implemented in 1.14 through Open Coded. This design uses code inline to optimize the extra overhead of defer key and introduces function data to funcdata to manage calls to panic 3. This optimization can drive the call overhead of defer from version 1.13~35nsLower to~6nsabout:

However, as a method to optimize the defer keyword, it is not enabled in all scenarios. Open encoding will only be enabled when the following conditions are met:

  • The number of defers of the function is less than or equal to 8;
  • The defer keyword of the function cannot be executed in a loop
  • The result of the return statement of the function and the defer statement is less than or equal to 15.

4. Reference

/golang/docs/part2-foundation/ch05-keyword/golang-defer/

This is the article about the precautions for using defer in Go. For more related content on using defer in Go, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!