What does this print? (defer with value vs pointer receivers)
type T struct{ name string }
func (t T) ValPrint() { fmt.Println("val:", t.name) }
func (t *T) PtrPrint() { fmt.Println("ptr:", t.name) }
func main() {
t := T{"a"}
defer t.ValPrint()
defer t.PtrPrint()
t.name = "b"
}
Output:
ptr: b
val: a
The receiver counts as an argument, so it is evaluated when the defer statement runs. For ValPrint, the method value t.ValPrint takes a copy of t at that moment (name = "a"). For PtrPrint, Go automatically takes &t, and that pointer is what gets saved. When the call finally runs, it sees the updated field. LIFO order puts the pointer version first.
The same rule applies to method values in general: f := t.ValPrint binds a copy right away. This leads to a well-known bug with sync.Mutex. If a type that contains a mutex has a method with a value receiver, that method's x.mu.Lock() and defer x.mu.Unlock() act on a copy of the mutex, so nothing is actually protected. go vet's copylocks check flags locks passed by value. What the interviewer wants to hear: a method value binds its receiver immediately.
More on Language Fundamentals & Types
- Q15In what order do deferred calls run, and what is wrong with using
deferinside a loop? - Q16How can a deferred function change a function's return value? What do
f()andg()return? - 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?
- Q21How does initialization work across packages, and what are the rules for
init()?