In Go, the keyword defer postpones the execution of a function until the surrounding function completes. This is convenient for resource cleanup and finalization. However, using defer in conjunction with methods that take pointers (*T) or values (T) has non-obvious nuances.
For safety concerns, Go was originally designed to guarantee the automatic invocation of cleanup code, regardless of the exit point from a function. However, the used receiver type (pointer or value) determines whether the object of the method calling defer is copied or the original object is modified.
If we call a method with a value receiver (T), the value of the structure is copied into defer. If a method with a pointer receiver (*T) is called, the method operates on the original object. As a result, changes made in the defer method by value remain unnoticed, while changes by pointer will reflect on the external structure. This leads to subtle bugs, especially when trying to change the state of an object through defer.
When designing methods and using defer, one should always consciously choose the type of receiver. Changes that need to persist until the end of the function should be made via a pointer.
Code example:
package main import "fmt" type Counter struct { Value int } func (c Counter) IncValue() { // method by value defer func() { c.Value++ // only the copy will increment fmt.Println("[Value receiver defer] Value:", c.Value) }() } func (c *Counter) IncPointer() { // method by pointer defer func() { c.Value++ // the original will increment fmt.Println("[Pointer receiver defer] Value:", c.Value) }() } func main() { c := Counter{Value: 10} c.IncValue() // Value will remain 10 c.IncPointer() // Value will become 11 fmt.Println("Original Value after calls:", c.Value) }
Key features:
1. Will the external object change if a method taking a value receiver is called in defer?
No, the original object will not change in defer, as the method works with a copy of the structure.
2. Can we rely on defer for guaranteed changes in the state of the structure through a value method?
No. Pointer methods should be used if you want the changes to reflect on the original object.
3. What happens if the fields of the structure are modified after the defer declaration?
The actions depend on how the receiver is passed: if the method/function receives a copy, changes after the defer declaration will not be visible in the deferred function.
func (c Counter) Demo() { defer fmt.Println("defer c.Value:", c.Value) // will capture current value c.Value = 42 // will not affect defer }
Wrote a connection closing function, updating the state using defer with a value method. It turned out that the closing flag was not being updated.
Pros:
Cons:
Used methods with pointer receivers for finalization, modifying the original object.
Pros:
Cons: