ProgrammingGo Developer

How does defer work in Go when interacting with return and panic, and what might be dangerous about changing return values within defer?

Pass interviews with Hintsage AI assistant

Answer.

Historically, the defer concept was introduced in Go for safely releasing resources and finalizing actions regardless of how the function execution finishes (either normally or due to a panic). However, the interaction of defer with return and panic has several pitfalls that even experienced developers often overlook.

The issue is that the order of evaluation of return values, the behavior of named return values, and changing these values in defer is significantly different from the typical behavior in many languages. Furthermore, errors can occur if there is an attempt to change already computed values in defer, causing unexpected behavior.

The solution is to always remember: the values returned by a function are computed BEFORE the defer runs, but if named results are used, they can be changed inside defer before the actual return from the function.

Example code:

func tricky() (res int) { defer func() { res = 42 // Changes the return value! }() return 10 } func main() { fmt.Println(tricky()) // Will print 42, not 10 }

Key features:

  • defer always runs after the evaluation of return arguments but before the actual return from the function.
  • Changing named return values inside defer affects the return value.
  • If a panic occurs, all deferred functions execute before the program exits or before recovery.

Tricky questions.

In what order do deferred (defer) functions execute?

They execute strictly in reverse order of their declaration (stack — LIFO).

func f() { defer fmt.Println("1") defer fmt.Println("2") } // Will print: 2, then 1

When are the parameters for defer-functions evaluated — at the time of the defer declaration or at its execution?

Parameters for defer-functions are evaluated at the time of the defer declaration, not at the call time.

func f() { i := 1 defer fmt.Println(i) // will print 1, even if i changes later i = 2 }

Can defer change an unnamed result of the function?

No. Only named return values can be changed in defer.

func f() int { defer func() { /* cannot change anything */ }() return 5 }

Common mistakes and anti-patterns

  • Expecting to change an anonymous (unnamed) result through defer.
  • Changing the return value through defer unnecessarily, leading to unpredictable behavior and complex bugs.
  • Not accounting for the order of evaluation and parameter passing in defer.

Real-life examples

Negative case

A young developer wanted to log the return code in defer and mistakenly changed the named return value, thus "overwriting" the actual result of the function.

Pros:

  • Quick fix of logic error.

Cons:

  • Returns incorrect value, hard debugging.

Positive case

In another situation, defer was used solely for resource releasing, logging, and did not change the return, while important values were explicitly assigned before return.

Pros:

  • Transparency, predictability of behavior.

Cons:

  • Additional lines of code need to be explicitly added if a side effect is required on exit stage.