Go

Why is defer f.Close() potentially a bug when writing files? How do you handle the Close error?

Question 317HardGo 1.22 to 1.25

For files opened for writing, Close (or the final flush) can be where the error shows up: buffered data, NFS, a full disk. defer f.Close() throws that error away, so you can report success while the data was never written. For read-only files, ignoring the Close error is usually fine.

func WriteFile(path string, data []byte) (err error) {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer func() {
		// Keep the first error, and still report the Close failure.
		err = errors.Join(err, f.Close())
	}()

	if _, err = f.Write(data); err != nil {
		return fmt.Errorf("write %s: %w", path, err)
	}
	return f.Sync() // flush to stable storage if durability matters
}

The deferred closure can assign to err because it is a named result. errors.Join returns nil when both errors are nil. Note that it returns a new wrapper whenever anything is non-nil, even for errors.Join(err, nil), so callers must use errors.Is rather than ==. A common alternative is if cerr := f.Close(); cerr != nil && err == nil { err = cerr }. For bufio.Writer, call Flush() and check its error before closing. For real atomicity, write to a temp file, then call Sync, Close and os.Rename.

More on Error Handling & panics

All 37 Error Handling & panics questions