Standard library patterns · 3 MIN READ

Two interfaces, one method each

Write a function that works on a file, a string and a test buffer.

io.Reader has Read. io.Writer has Write. Files, network connections, HTTP bodies, gzip streams, strings.Reader and bytes.Buffer all satisfy one or both.

Accepting them instead of a concrete type is what makes Go code testable without mocks: the test passes a bytes.Buffer and reads the result back.

SEE THE IDEA

Separate the operation from the transport

  1. 01

    Source

    go
    io.Reader

    Produces bytes

  2. 02

    Transformation

    go
    copy or transform

    Works against the small contracts

  3. 03

    Destination

    go
    io.Writer

    Accepts bytes

The same operation can use files, memory buffers or other implementations.

In practice

Accept interfaces that describe only the I/O behavior the function uses. A reader may return bytes and an error in the same call; process those bytes before the error. Standard helpers such as io.Copy and io.ReadAll handle the protocol details, but ReadAll uses memory proportional to the input.

Watch out

Do not assume one Read fills the requested buffer. For an exact byte count, use io.ReadFull.

Official reference ↗

Work through the exercise

go
package main

import (
	"os"
	"strings"
)

// WriteUpper copies src to dst, upper-cased. It should work with any
// reader and writer, not just files.
func WriteUpper(dst *os.File, src *strings.Reader) error {
	return nil
}

func main() {}
Try it yourself

Trace each value and write down your reasoning. Copy the source into GopherForge or your local Go environment to compile and check it. This web page does not execute your code or send it to a compiler service.

Progress stays in this browser. No account needed.