How can a deferred function change a function's return value? What do f() and g() return?
Question 16HardGo 1.22 to 1.25
func f() (n int) {
defer func() { n *= 2 }()
return 3
}
func g() int {
n := 3
defer func() { n *= 2 }()
return n
}
func safeDiv(a, b int) (q int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
return a / b, nil
}
Answer: f() returns 6, g() returns 3. safeDiv(1, 0) returns 0, recovered: runtime error: integer divide by zero.
return X is not a single step. First it assigns X to the result parameters. Then the deferred functions run. Then the function actually returns. With named results, a deferred closure can read and change the result after the return statement has set it. In g, the result has no name. The value of n is copied into a hidden result slot, so changing the local n afterwards has no effect.
Common real-world uses:
- Turning a panic into an error, as in
safeDiv. - Wrapping errors, e.g.
defer func(){ if err != nil { err = fmt.Errorf("op: %w", err) } }(). - Keeping a
Closeerror:defer func(){ if cerr := f.Close(); err == nil { err = cerr } }(). Better still, useerrors.Join(err, cerr).
More on Language Fundamentals & Types
- Q14When are the arguments to a deferred call evaluated? What does this print?
- Q15In what order do deferred calls run, and what is wrong with using
deferinside a loop? - Q17What does this print? (defer with value vs pointer receivers)
- Q18What are the rules for
recover? Why doesn'tdefer recover()stop a panic? - Q19Do deferred functions run on
os.Exit,log.Fatal,runtime.Goexitandpanic? - Q20In what order are package-level variables initialized? What does this print?