A defer statement schedules a call for when its surrounding function returns. It is useful for closing a resource without repeating cleanup before every return. The important boundary is the function, not the nearest loop. section 4.6
func readFiles(ch <-chan string) error {
for path := range ch { #1
file, err := os.Open(path) #2
if err != nil {
return err
}
defer file.Close() #3
// Do something with file
}
return nil
}
Mistake: deferring file closure inside a long-running loop. Every Close waits for readFiles to return, so file descriptors accumulate; if it never returns, they remain open. section 4.6
func readFiles(ch <-chan string) error {
for path := range ch {
if err := readFile(path); err != nil { #1
return err
}
}
return nil
}
func readFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close() #2
// Do something with file
return nil
}
Move each iteration’s work into readFile. Its deferred Close runs when that call returns, at the end of the iteration. An immediately invoked closure can provide the same boundary, but a separate function can be clearer and tested independently. section 4.6
Another mistake is assuming a deferred call reads its arguments when it runs. Arguments are evaluated at the defer statement. A method receiver is evaluated then, too: later changes to a value receiver are not reflected in the deferred call, while changes through a pointer receiver can be. section 6.6.1 section 6.6.2
const (
StatusSuccess = "success"
StatusErrorFoo = "error_foo"
StatusErrorBar = "error_bar"
)
func f() error {
var status string
defer notify(status) #1
defer incrementCounter(status) #2
if err := foo(); err != nil {
status = StatusErrorFoo #3
return err
}
if err := bar(); err != nil {
status = StatusErrorBar #4
return err
}
status = StatusSuccess #5
return nil
}
Both deferred calls receive the initial empty status, even though status changes before return. Pass a pointer if the functions accept one, or defer a closure that reads status when it runs. section 6.6.1
func f() error {
var status string
defer func() { #1
notify(status) #2
incrementCounter(status) #3
}()
// The rest of the function is unchanged
}
The closure delays reading status until the deferred function executes, without changing notify or incrementCounter to accept pointers. section 6.6.1
Cleanup can fail. A plain deferred rows.Close() discards its error. If the error matters, log it or use a closure and a named result to return it; preserve an earlier query or scan error rather than replacing it with a close error. A return inside the deferred closure returns only from that closure. section 7.7
func (h handler) getStatusCode(body io.Reader) (int, error) {
resp, err := h.client.Post(h.url, "application/json", body)
if err != nil {
return 0, err
}
defer func() { #1
err := resp.Body.Close()
if err != nil {
log.Printf("failed to close response: %v\n", err)
}
}()
return resp.StatusCode, nil
}
An HTTP response body needs closing even when only its status code is used. This deferred closure logs a close failure. When repeated requests need connection reuse, reading the body before closing it also matters. section 10.5.1


