io.Reader and io.Writer
The io package defines two one-method interfaces: Reader and Writer.
A Reader has one method. You hand Read a slice, it fills as much of it as it can, and tells you how many bytes it managed. The slice is the buffer, so you control how much memory the read costs.
Read the data out of buf[:n] rather than trusting the whole slice: the tail is whatever was there before.
r := strings.NewReader("hello from a reader")
buf := make([]byte, 5)
n, err := r.Read(buf)
if err != nil {
log.Fatal(err)
}
fmt.Println(n, string(buf[:n]))Most of the time you want everything, and io.ReadAll loops for you until the reader reports EOF. It allocates as it goes, which is fine for a config file and dangerous for a request body, so pair it with io.LimitReader when the source is not yours.
data, err := io.ReadAll(strings.NewReader("all of it, in one call"))
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))A Writer is the mirror image: one method that takes bytes and reports how many it took. io.WriteString saves you converting a string to a slice first.
os.Stdout is an *os.File, and a file is a Writer like any other. Nothing here knows it is writing to a terminal.
written, err := io.WriteString(os.Stdout, "straight to stdout\n")
if err != nil {
log.Fatal(err)
}
fmt.Println(written)Once something reads and something else writes, io.Copy joins them and moves the bytes across in a fixed-size buffer. This is the line that streams a file to an HTTP response without loading it into memory.
A strings.Builder is a Writer, so it can be the destination without any adapter.
var sb strings.Builder
copied, err := io.Copy(&sb, strings.NewReader("copied across"))
if err != nil {
log.Fatal(err)
}
fmt.Println(copied, sb.String())Four different sources and destinations, none of which know anything about each other. That is the whole payoff: write a function that takes a Reader and it works against a file, a socket, an HTTP body, or a string in a test.
$ go run readerwriter.go
5 hello
all of it, in one call
straight to stdout
19
13 copied acrossMore examples
io.Copy moves bytes from a Reader to a Writer.
r := strings.NewReader("hello, reader")
w := &bytes.Buffer{}
n, err := io.Copy(w, r)
if err != nil {
log.Fatal(err)
}
fmt.Println(n, w.String())13 hello, readerA standard guard against unbounded request bodies.
body := strings.NewReader(strings.Repeat("x", 2_000_000))
limited := io.LimitReader(body, 1<<20) // cap at 1MB
data, err := io.ReadAll(limited)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(data))1048576Any type with a matching Write method satisfies io.Writer.
type stringWriter struct {
lines []string
}
func (w *stringWriter) Write(p []byte) (int, error) {
w.lines = append(w.lines, string(p))
return len(p), nil
}
sw := &stringWriter{}
logger := log.New(sw, "", 0)
logger.Println("captured message")
fmt.Println(len(sw.lines), strings.TrimSpace(sw.lines[0]))1 captured message| Function | Description |
|---|---|
Reader interface { Read(p []byte) (n int, err error) } n, err := r.Read(buf) | Process n bytes before checking err, both can be set. |
Writer interface { Write(p []byte) (n int, err error) } n, err := w.Write(buf) | n < len(p) always comes with a non-nil err. |
Copy(dst Writer, src Reader) (written int64, err error) n, err := io.Copy(w, r) | Bytes already landed in dst; this is a receipt. |
CopyN(dst Writer, src Reader, n int64) (int64, error) written, err := io.CopyN(w, r, 512) | Errors if fewer than n bytes were available. |
ReadAll(r Reader) ([]byte, error) data, err := io.ReadAll(r) | Everything up to EOF, as a new slice. |
ReadFull(r Reader, buf []byte) (n int, err error) n, err := io.ReadFull(r, buf) | Fills buf in place; read the data from buf, not n. |
WriteString(w Writer, s string) (n int, err error) n, err := io.WriteString(w, "hi") | Same receipt shape as Writer.Write. |
MultiReader(readers ...Reader) Reader r := io.MultiReader(r1, r2) | New Reader; pass it to Copy/ReadAll like any other. |
MultiWriter(writers ...Writer) Writer w := io.MultiWriter(os.Stdout, f) | New Writer that fans out to all of them. |
TeeReader(r Reader, w Writer) Reader t := io.TeeReader(r, &buf) | Reading it also mirrors bytes into w. |
LimitReader(r Reader, n int64) Reader lr := io.LimitReader(r, 1<<20) | New Reader, EOF after n bytes. |
Pipe() (*PipeReader, *PipeWriter) pr, pw := io.Pipe() | Writes block until read. Pair across goroutines. |
NopCloser(r Reader) ReadCloser rc := io.NopCloser(r) | Adds a Close that does nothing. |
Discard Writer io.Copy(io.Discard, r) | A ready-made value, not a function. |
Related
Composed interfaces
Larger interfaces embed Reader and Writer instead of redeclaring their methods.
| Interface | Composed of |
|---|---|
| ReadWriter | Reader + Writer |
| ReadCloser | Reader + Closer |
| WriteCloser | Writer + Closer |
| ReadWriteCloser | Reader + Writer + Closer |