Single method interfaces end up being extremely powerful — more so than even the language creators thought. Let’s have a look at io.Reader:

type Reader interface {
  Read(p []byte) (n int, err error)
}

Pretty simple; a Reader is any type that implements the Read method. For those unfamiliar with these interfaces; you pass in a slice of bytes, and Read is asked to fill it with its data — which is does until it runs out of data. It returns the number of bytes read (in n) or an error if something goes wrong. Additionally, if it has finished reading, it will return a special marker error called io.EOF (end of file).

Examples of readers

There are many kinds of Reader types available in the standard library, and you’ve almost certainly used at least one of them. If you open a file for reading, the object returned is an os.File, which is a Reader (it implements the Read method):

var r io.Reader
var err error
r, err = os.Open("file.txt")

You can also make a Reader from a normal string using strings.NewReader:

var r io.Reader
r = strings.NewReader("Read will return these bytes")

The body data from an http.Request is a Reader:

var r io.Reader
r = request.Body

A bytes.Buffer is a Reader:

var r io.Reader
var buf bytes.Buffer
r = &buf

There are many more throughout the standard library — and in most third-party packages too, since it’s good practice to use them wherever you can.

Using readers

Now we have a few Reader types — let’s explore ways in which they can be used. You can read from them directly (this turns out to be the least useful use case):

p := make([]byte, 256)
n, err := r.Read(p)

ioutil.ReadAll lets you read everything from a Reader, and get the raw []byte data:

b, err := ioutil.ReadAll(r)

io.Copy lets you read ALL bytes from an io.Reader, and write it to an io.Writer:

n, err := io.Copy(w, r)

The JSON decoder lets you decode directly from a Reader:

err := json.NewDecoder(r).Decode(v)

If you’re reading bytes that have been gzipped, you can wrap the io.Reader in a gzip.Reader:

r = gzip.NewReader(r)

Now reading from the new reader will decompress as you read.

Take io.Reader when you can

If you’re designing a package or utility (even if it’s an internal thing that nobody will ever see) rather than taking in strings or []byte slices, consider taking in an io.Reader if you can for data sources. Because suddenly, your code will work with every type that implements io.Reader. So this:

func Reverse(s string) (string, error)

Could become:

func Reverse(r io.Reader) io.Reader

Then if someone wants to use it with a string, they can:

r = Reverse(strings.NewReader("Make me backwards"))

f, err := os.Open("file.txt")
if err != nil {
  log.Fatalln(err)
}
r = Reverse(f)

func handle(w http.ResponseWriter, r *http.Request) {
  rev := Reverse(r.Body)
  // etc...
}

Use io.Reader (and io.Writer) whenever you’re dealing with streams of data. And this goes for all single method interfaces from the standard library.