Go, End to End
Working with Files and I/O
io.Reader and io.Writer
If Go's standard library has one idea that pays for the whole design, it's these two interfaces:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}That's it. Anything that can produce a stream of bytes implements Reader; anything that can consume one implements Writer. Files, network connections, in-memory buffers, HTTP request/response bodies, compressors, hashers, they all implement these same two interfaces, which means code written against io.Reader/io.Writer works with all of them without modification. A function that copies data doesn't need to know or care whether it's copying from a file to a network socket or from a string to an in-memory buffer.
Working with files
The os package is where file operations live: os.Open for reading, os.Create for writing (truncating if it exists), os.OpenFile when you need finer control over flags/permissions. Always pair opening with defer f.Close() right after checking the open succeeded, same pattern as the defer chapter.
f, err := os.Open("data.txt")
if err != nil {
return err
}
defer f.Close()
// f is an io.Reader, use it with anything that accepts onebufio for buffered, line-based reading
Reading a file byte by byte or in small unbuffered reads is slow (each Read call can mean a system call). bufio.NewReader/bufio.NewScanner wrap a reader with an internal buffer, and Scanner in particular makes line-by-line reading a one-liner:
bufio.NewWriter is the write-side equivalent: batches small writes into fewer underlying system calls, but remember to call Flush() before the program exits or you'll lose whatever's still sitting in the buffer.
Try it yourself: use bufio.NewScanner to count how many lines in a multi-line string contain the word "error" (case-insensitive).