ProgrammingBackend Developer

What are the features and pitfalls of using defer with methods and functions that take pointers and values in Go?

Pass interviews with Hintsage AI assistant

Answer.

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.

Background

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.

The Problem

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.

The Solution

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:

  • defer always receives a copy of the arguments and receivers at the time of declaration.
  • Methods with value receivers do not affect the external object in defer.
  • Methods with pointer receivers modify the original through defer.

Trick Questions.

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 }

Common Mistakes and Anti-patterns

  • Trying to change the original object through a method with a value receiver inside defer.
  • Not paying attention to the copying of structures when using defer.

Real-life Example

Negative Case

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:

  • Code is concise and readable.

Cons:

  • Cleanup did not occur — connections leaked.

Positive Case

Used methods with pointer receivers for finalization, modifying the original object.

Pros:

  • State changes correctly, resources are cleaned up.

Cons:

  • Strict control over when to use pointers and when to use values is required.