09 — io.Reader & io.Writer

Go's streaming I/O: the two tiny interfaces every byte source and sink implements, the helpers that pump between them, and how to write your own. Every example is live — edit it and press Run.

The two interfaces and the helpers

An io.Reader has one method Read; an io.Writer has Write. Files, sockets, bytes.Buffer, HTTP bodies — all implement them, so code composes freely. io.Copy pumps any reader into any writer; bufio.Scanner tokenises by line; and because Writer is just an interface, your own type can be one. io.TeeReader mirrors bytes to a writer as they're read.

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "strings"
)

func ReadAll(r io.Reader) (string, error) {
    var buf bytes.Buffer
    _, err := io.Copy(&buf, r)   // pump reader -> buffer
    return buf.String(), err
}

func Lines(r io.Reader) ([]string, error) {
    sc := bufio.NewScanner(r)
    var out []string
    for sc.Scan() {              // one line per iteration
        out = append(out, sc.Text())
    }
    return out, sc.Err()
}

type CountingWriter struct{ n int }

func (c *CountingWriter) Write(p []byte) (int, error) {
    c.n += len(p)               // our own io.Writer
    return len(p), nil
}

func main() {
    s, _ := ReadAll(strings.NewReader("hello world"))
    fmt.Println(s)

    lines, _ := Lines(strings.NewReader("one\ntwo\nthree"))
    fmt.Println(lines)

    var cw CountingWriter
    io.Copy(&cw, strings.NewReader("hello world"))
    fmt.Println(cw.n)

    var sink bytes.Buffer
    tee := io.TeeReader(strings.NewReader("payload"), &sink)
    data, _ := io.ReadAll(tee)  // reading tee also fills sink
    fmt.Printf("%s %s\n", data, sink.String())
}

Output:

hello world
[one two three]
11
payload payload

End of stream is the value io.EOF, not a magic -1 — though the helpers above handle it for you. A pointer receiver on Write is needed because it mutates n, and it's the *CountingWriter that satisfies io.Writer.

Recap

ConceptIn Go
Read from anythingio.Reader — Read(p []byte)
Write to anythingio.Writer — Write(p []byte)
Pump reader → writerio.Copy(dst, src)
In-memory streambytes.Buffer (Reader and Writer)
Read linesbufio.Scanner, check sc.Err()
Mirror while readingio.TeeReader(r, w)

Next: 10 — Maps & JSON.