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:
-
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
- The compiler uses the same memory address for loop variable
-
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.