Featured image of post A Bug When Using defer in Loops in Go

A Bug When Using defer in Loops in Go

A small issue encountered in Go

First note: Using defer inside loops is generally considered a bad practice

  • Came across this issue while browsing the community:
package main

import "fmt"

type Test struct {
    name string
}

func (t *Test) Close() {
    fmt.Println(t.name, " closed")
}

func main() {
    ts := []Test{{"a"}, {"b"}, {"c"}}
    for _, t := range ts {
        defer t.Close()
    }
}
// Output:
// c closed
// c closed
// c closed

The output shows three c closed results.
The top-voted answer claimed this happens because defer creates an external variable that gets overwritten in the loop. But the code didn’t seem to match that explanation, so I investigated further.


Eventually identified the issue as value receivers calling pointer methods. Modified test code:

package main

import (
    "fmt"
    "unsafe"
)

type Test struct {
    name string
}

func (t *Test) Close() {
    fmt.Println(unsafe.Pointer(t), t.name, " closed")
}

func main() {
    // Using pointer slice
    ts := []*Test{{"a"}, {"b"}, {"c"}}
    for _, t := range ts {
        defer t.Close()
    }
}
// Output (with pointer addresses):
// 0xc000044260 c  closed
// 0xc000044250 b  closed
// 0xc000044240 a  closed

Root cause: Calling pointer methods on values directly.
Original code with address printing:

package main

import (
    "fmt"
    "unsafe"
)

type Test struct {
    name string
}

func (t *Test) Close() {
    fmt.Println(unsafe.Pointer(t), t.name, " closed")
}

func main() {
    ts := []Test{{"a"}, {"b"}, {"c"}}
    for _, t := range ts {
        fmt.Println(unsafe.Pointer(&t))
        defer t.Close()
    }
}
// Output shows same address:
// 0xc000044240
// 0xc000044240
// 0xc000044240
// 0xc000044240 c  closed
// 0xc000044240 c  closed
// 0xc000044240 c  closed

Official Explanation from Go Spec:

“Value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers. However, when the value is addressable, the compiler automatically inserts the address operator to allow calling pointer methods on values.”

Key Points:

  1. When calling pointer methods on values in a loop:

    • The compiler uses the same memory address for loop variable t in each iteration
    • All defer statements capture the same memory address
    • The final value at that address becomes c after loop completion
  2. When using pointer slices:

    • Each element has a distinct memory address
    • defer captures the actual pointer value for each iteration

Conclusion: Avoid calling pointer methods on loop variables when using defer. If necessary, create local copies or use pointer slices directly.