漫行记Wandering
Journal 2,234 字 8 分钟 Golang

Go Defer

What Defer is

Defer is a powerful feature in Go that allows you to schedule a function call to be executed just before the surrounding function returns. This is particularly useful for resource management, such as closing files, releasing locks, or cleaning up resources.

func readFile() {
    file, _ := os.Open("example.txt")
    defer file.Close()
    ...

Even if readFile hits return early or panics, file.Close() will still be called.

Basic Behavior

  • Order: Deferred functions are executed in LIFO (Last In, First Out) order.
func main() {
    defer fmt.Println("First")
    defer fmt.Println("Second")
    defer fmt.Println("Third")
    fmt.Println("Function Body")
}
// Output:
    Function Body
    Third
    Second
    First
  • Arguments: Arguments to deferred functions are evaluated immediately when the defer statement is executed, not when the function is called.
func main() {
    x := 10
    defer fmt.Println(x) // x is evaluated here, so it prints 10
    x = 20
// Output:
    10
}

but if you write a code like below, the closure captures x by reference, which means deferred function will see the current value of x at execution time, not at defer-time

func main() {
    x := 10
    defer func() { fmt.Println(x) }()
    x = 20
// Output:
    20
}

Under the Hood

when you write code like defer f(a,b), Go compiler does two main things.

  1. Evaluates arguments immediately
    • a and b are evaluated at that point
    • Stored in the _defer node
  2. Calls a runtime helper(runtime,deferproc)
    • Allocates a _defer struct
    • Links it to the current goroutine’s defer list
    • Stores:
      • Function pointer
      • Arguments
      • Frame pointer(sp) of the current function
      • Link to next dfer node

So the struct looks like below:

+-------------------------------+
|           _defer node          |
+-------------------------------+
| fn     | pointer to function   | <- deferred function to call
+--------+----------------------+
| frame  | pointer to stack frame| <- which function frame owns this defer
+--------+----------------------+
| args   | evaluated arguments   | <- values captured at defer creation
+--------+----------------------+
| link   | *_defer               | <- next node in goroutine’s defer list
+-------------------------------+
| other  | runtime bookkeeping   | <- panic state, active flags, etc.
+-------------------------------+

When the surrounding function returns, Go runtime:

  1. Calls runtime.deferreturn(generated by the compiler for each function exit)
  2. Walks the linked list of defers
  3. Executes only the nodes that belong to the current function frame (Uses frame pointer to check)
  4. Pops the executed nodes from the list

Please note, In the Go runtime, there is actually just one linked list per goroutine, not one per function . But each _defer node knows which stack frame it belongs to. That’s how the runtime knows which defers to execute when a function returns.

Usage

  1. Resource cleanup: Please use defer immediately after acquiring a resource like files, network connections, timers to ensure it gets released properly.
  2. Mutexes and locks:
    mu.Lock()
    defer mu.Unlock() 
  3. Handling panics with recover:
    defer func() {
         if r := recover(); r != nil {
              fmt.Println("Recovered from panic:", r)
         }
    }()
← 随笔