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.Writercontracts; theReadloopio.Copy,io.ReadAll,io.TeeReader,io.MultiWriter,io.LimitReaderio.EOFas a normal sentinel, not an error
📖 Reading / Sources¶
-
iopackage docs - The Go Programming Language §7.1–7.2 (interfaces & io)
- Effective Go — Interfaces
📝 Notes¶
io.Readeris a single method:Read(p []byte) (n int, err error). It fillsp, returns how many bytesnit wrote, and an error. This is the universal "source of bytes" → [[io-reader]].io.WriterisWrite(p []byte) (n int, err error)— the universal "sink for bytes".*os.File,*bytes.Buffer,*strings.Builder,net.Conn, andhttp.ResponseWriterall satisfy it.- The Read contract is the #1 trap: a
Readmay returnn > 0and a non-nil error (oftenio.EOF) in the same call. Always process the firstnbytes before checking the error → [[read-loop-contract]]. io.EOFis a sentinel value, not a failure. The loop ends whenReadreturnsio.EOF; treat any other error as real.io.Copy(dst, src)is the universal pump — it loopsRead→Writeuntil EOF, usingsrc'sWriteToordst'sReadFromfast paths when available. Prefer it over hand-rolled loops.io.ReadAll(r)drains a reader into a[]byte(replaced the deprecatedioutil.ReadAll).- Composition helpers turn small interfaces into big leverage:
TeeReader(r, w)mirrors reads intow;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, andt.Runfixtures 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) toio.Copyinstead of&dst→bytes.Bufferdoes not implementio.Writerby value becauseWritehas a pointer receiver. - Checked
errbefore consumingnbytes in a manual loop → dropped the last chunk that arrived together withio.EOF.
❓ Open Questions¶
- When does
io.Copyactually pick theReadFrom/WriteTofast path vs. its internal buffer?
🧠 Active Recall (answer without looking)¶
-
Q: Why must you handle
nbeforeerrin aReadloop?
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. -
Q: Is
io.EOFan 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.WriterTofast-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)