Skip to content

Day 029 — io.Reader & io.Writer

Month 2 · Week 1 · ⬅ Day 028 · Day 030 ➡ · Journal index

🎯 Learning Objective

Understand the io.Reader/io.Writer one-method interfaces and the small composable helpers (io.Copy, TeeReader, MultiWriter, LimitReader) that make every byte stream in Go interchangeable.

📚 Topics

  • io.Reader / io.Writer contracts; the Read loop
  • io.Copy, io.ReadAll, io.TeeReader, io.MultiWriter, io.LimitReader
  • io.EOF as a normal sentinel, not an error

📖 Reading / Sources

📝 Notes

  • io.Reader is a single method: Read(p []byte) (n int, err error). It fills p, returns how many bytes n it wrote, and an error. This is the universal "source of bytes" → [[io-reader]].
  • io.Writer is Write(p []byte) (n int, err error) — the universal "sink for bytes". *os.File, *bytes.Buffer, *strings.Builder, net.Conn, and http.ResponseWriter all satisfy it.
  • The Read contract is the #1 trap: a Read may return n > 0 and a non-nil error (often io.EOF) in the same call. Always process the first n bytes before checking the error → [[read-loop-contract]].
  • io.EOF is a sentinel value, not a failure. The loop ends when Read returns io.EOF; treat any other error as real.
  • io.Copy(dst, src) is the universal pump — it loops ReadWrite until EOF, using src's WriteTo or dst's ReadFrom fast paths when available. Prefer it over hand-rolled loops.
  • io.ReadAll(r) drains a reader into a []byte (replaced the deprecated ioutil.ReadAll).
  • Composition helpers turn small interfaces into big leverage: TeeReader(r, w) mirrors reads into w; MultiWriter(w...) fans one write out to many; LimitReader(r, n) caps input — the standard defense against unbounded request bodies.
  • Accept interfaces, return structs: a function should take io.Reader, not *os.File, so it works with files, strings, sockets, and t.Run fixtures alike → [[accept-interfaces]].

💻 Code Examples

// io.Copy pumps any Reader into any Writer until EOF.
src := strings.NewReader("hello, io\n")
var dst bytes.Buffer
n, err := io.Copy(&dst, src) // &dst — Write has a pointer receiver
fmt.Printf("moved %d bytes: %q\n", n, dst.String())

Full code: examples/month-02/io-basics/main.go · Run: go run ./examples/month-02/io-basics

🏋️ Exercises / Practice

Exercise Status Link
wc-style line/word/byte counter over io.Reader exercises/month-02/week-1/linestats

🐛 Mistakes Made

  • Passed dst (value) to io.Copy instead of &dstbytes.Buffer does not implement io.Writer by value because Write has a pointer receiver.
  • Checked err before consuming n bytes in a manual loop → dropped the last chunk that arrived together with io.EOF.

❓ Open Questions

  • When does io.Copy actually pick the ReadFrom/WriteTo fast path vs. its internal buffer?

🧠 Active Recall (answer without looking)

  1. Q: Why must you handle n before err in a Read loop?

    A Because the `io.Reader` contract permits returning `n > 0` and a non-nil error (e.g. `io.EOF`) in the same call. Checking `err` first would silently drop the final bytes.

  2. Q: Is io.EOF an error condition you should log and abort on?

    A No — it's the normal, expected signal that a stream is exhausted. Break the loop on `io.EOF`; only treat *other* errors as failures.

🪶 Feynman Reflection

A Reader is anything you can pull bytes out of; a Writer is anything you can push bytes into. Because each is just one method, the whole standard library snaps together like LEGO: files, strings, network sockets, and buffers are all the same shape, so io.Copy can move bytes between any two of them without caring what they really are.

🕳️ Knowledge Gaps

  • The io.ReaderFrom/io.WriterTo fast-path interfaces — revisit when I profile copies.

✅ Summary

I can read from and write to any byte stream through the io interfaces, write a correct Read loop, and compose io.Copy/TeeReader/MultiWriter/LimitReader.

⏭️ Next Steps / Prep for Tomorrow

  • Day 030: bufio — why and how to buffer these streams for speed.

Time spent Difficulty Confidence
90 min 🟦🟦⬜⬜⬜ 🟦🟦🟦⬜⬜

Suggested commit: feat(examples): io.Reader/Writer interfaces and io helpers (day 029)