Go

What is the io.Reader error contract, and why is if err != nil { return } wrong there?

Question 325HardGo 1.22 to 1.25

Read(p) can return n > 0 and a non-nil error in the same call, including io.EOF. The contract says callers should process the n bytes before looking at the error. Checking err first loses the final chunk of data. io.EOF is also a plain sentinel that readers return unwrapped, and it means clean end of input, not failure. io.ErrUnexpectedEOF signals truncated input.

func count(r io.Reader) (int, error) {
	buf := make([]byte, 4096)
	total := 0
	for {
		n, err := r.Read(buf)
		total += n // consume data FIRST
		if err == io.EOF {
			return total, nil // normal termination
		}
		if err != nil {
			return total, err
		}
	}
}

In the general case, a function returning (T, error) with a non-nil error makes T meaningless unless the documentation says otherwise. io.Reader and io.Writer (which must return a non-nil error if n < len(p)) are the notable exceptions. Higher-level helpers such as io.ReadAll, io.ReadFull and bufio.Scanner handle the contract for you. ReadAll treats EOF as success, and Scanner.Err() returns nil at EOF. Implementations should never return (0, nil) repeatedly.

More on Error Handling & panics

All 37 Error Handling & panics questions