What's the idiomatic way to handle errors from deferred cleanup functions?

5 minadvancedgodefererror-handlingcleanup

Quick Answer

A deferred cleanup call, like defer f.Close(), can itself return an error, but that error is easy to silently drop since defer statements don't naturally chain into the enclosing function's return value. The idiomatic fix is a named return value plus a deferred closure that checks the cleanup's error and merges it into the function's own return, often only if there wasn't already a more important error: defer func() { if cerr := f.Close(); cerr != nil && err == nil { err = cerr } }(). For less critical cleanup (like closing a response body you've already fully read), it's common and acceptable to just ignore the close error, or log it, rather than let a minor cleanup failure mask the real result of the function.

Detailed Answer

f.Close() returning an error that gets silently dropped is one of the most common small correctness gaps in real Go code.

The common, slightly-lossy pattern

func writeFile(path string, data []byte) error {
    f, err := os.Create(path)
    if err != nil {
        return err
    }
    defer f.Close()   // return value silently discarded

    _, err = f.Write(data)
    return err
}

If f.Close() fails, for instance because a buffered write never actually flushed to disk, that failure is completely invisible to the caller.

Capturing the deferred error properly

func writeFile(path string, data []byte) (err error) {
    f, createErr := os.Create(path)
    if createErr != nil {
        return createErr
    }
    defer func() {
        if cerr := f.Close(); cerr != nil && err == nil {
            err = cerr   // only surface the close error if nothing else already failed
        }
    }()

    _, err = f.Write(data)
    return err
}

The named return err lets the deferred closure read and modify the actual value the function sends back, which is exactly the mechanism named returns exist for.

When it's fine to just ignore it

resp, err := http.Get(url)
if err != nil {
    return err
}
defer resp.Body.Close()   // ignoring this error is standard practice here

For something like an HTTP response body you've already fully read, a failure to close cleanly is rarely actionable or important enough to plumb through the function's return value. The general guidance: capture and surface cleanup errors when they indicate real data-loss risk, like a file write not being durably flushed. It's reasonable to ignore them once the resource's job was already done and a close failure carries little practical consequence.

Related Resources