Validate io.Write and io.Read calls

In Go it's common to have an io.Writer wrapping another io.Writer. The gzip.Writer type in the standard library is a good example. Writes to a gzip.Writer eventually result in writes to the wrapped io.Writer (the one that was provided to gzip.NewWriter).

package gzip // import "compress/gzip"

type Writer struct { ... }

func NewWriter(w io.Writer) *Writer
func (z *Writer) Write(p []byte) (int, error)

Misbehaving Writers

If your package implements an io.Writer that wraps another io.Writer it helps to check for misbehaving wrapped io.Writers. A misbehaving io.Writer, for example, is one that returns a nil error along with n < len(p)1, either because it is unaware of the io.Writer interface requirements or because of a true logical bug in its implementation.

If your package propagates (n, err) return values from a misbehaving underlying io.Writer without checking, as in:

package mypkg

type Writer struct{ w io.Writer }

func (w *Writer) Write(p []byte) (int, error) {
    return w.w.Write(p)
}

then it makes it harder for clients of your package to detect and debug the underlying misbehaving io.Writer, because the bug is propagated instead of being detected at the earliest. Additionally your Write method too now violates the io.Writer interface.

So it helps to validate the return values of Write calls to the underlying io.Writer.

Validating a Write

Validating a Write involves checking two requirements from the io.Writer interface.

Write returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early.

and

Write must return a non-nil error if it returns n < len(p).

func (w *Writer) Write(p []byte) (int, error) {
    return validatedWrite(w.w, p)
}
func validatedWrite(w io.Writer, p []byte) (int, error) {
    m, err := w.Write(p)
    if m < 0 || m > len(p) {
        panic("invalid Write count")
    }
    if m < len(p) && err == nil {
        return m, io.ErrShortWrite
    }
    return m, err
}

Reads

The same applies to io.Readers and Read calls!

func validatedRead(r io.Reader, p []byte) (int, error) {
    n, err := r.Read(p)
    if n < 0 || n > len(p) {
        panic("invalid Read count")
    }
    return n, err
}
  1. This violates the io.Writer interface, whose documentation says, "Write must return a non-nil error if it returns n < len(p)." ↩︎