io and bufio
The io.Reader and io.Writer interfaces
Section titled “The io.Reader and io.Writer interfaces”The io package defines two small but foundational interfaces:
type Reader interface { Read(p []byte) (n int, err error)}
type Writer interface { Write(p []byte) (n int, err error)}These two interfaces are the lingua franca of I/O in Go. Dozens of types implement them: files, network connections, HTTP request and response bodies, in-memory buffers, and more. Functions that accept io.Reader or io.Writer work with all of them without modification.
strings.NewReader — an in-memory Reader
Section titled “strings.NewReader — an in-memory Reader”strings.NewReader(s) wraps a string as an io.Reader. This is invaluable in tests and examples — you can exercise any function that reads from a stream without touching the filesystem.
r := strings.NewReader("hello, world")data, _ := io.ReadAll(r)fmt.Println(string(data)) // hello, worldbytes.Buffer — a read/write buffer
Section titled “bytes.Buffer — a read/write buffer”bytes.Buffer implements both io.Reader and io.Writer. You write into it and read back out:
var buf bytes.Bufferfmt.Fprintln(&buf, "first line")fmt.Fprintln(&buf, "second line")content, _ := io.ReadAll(&buf)fmt.Print(string(content))fmt.Fprintln accepts any io.Writer, so you can redirect formatted output to a buffer, a file, or a network connection with zero code changes.
bufio.Scanner — line-by-line reading
Section titled “bufio.Scanner — line-by-line reading”bufio.Scanner wraps any io.Reader and provides convenient line-by-line (or token-by-token) reading. The default split function is bufio.ScanLines.
scanner := bufio.NewScanner(r)for scanner.Scan() { fmt.Println(scanner.Text()) // one line at a time}if err := scanner.Err(); err != nil { log.Fatal(err)}scanner.Scan() returns false when the input is exhausted or an error occurs. Always check scanner.Err() after the loop — a false return alone does not distinguish EOF from an error.
io helper functions
Section titled “io helper functions”The io package also provides useful helpers:
io.ReadAll(r) // read until EOF, return []byteio.Copy(dst, src) // copy from Reader to Writerio.MultiReader(r1, r2) // chain readers sequentiallyio.Discard // Writer that discards all bytes (like /dev/null)io.LimitReader(r, n) // read at most n bytespackage main
import ( "bufio" "bytes" "fmt" "io" "strings")
// printLines accepts any io.Reader — works with files, buffers, or stringsfunc printLines(r io.Reader) { scanner := bufio.NewScanner(r) for scanner.Scan() { fmt.Println(">", scanner.Text()) }}
func main() { // strings.NewReader implements io.Reader r := strings.NewReader("first line\nsecond line\nthird line") printLines(r)
// bytes.Buffer implements both io.Reader and io.Writer var buf bytes.Buffer fmt.Fprintln(&buf, "written to buffer") fmt.Fprintln(&buf, "another line") content, _ := io.ReadAll(&buf) fmt.Print(string(content))
// io.MultiReader chains two readers sequentially r1 := strings.NewReader("hello ") r2 := strings.NewReader("world\n") combined := io.MultiReader(r1, r2) io.Copy(io.Discard, combined) fmt.Println("MultiReader composed two readers")
// bufio.Scanner over in-memory CSV csv := "alice,30\nbob,25\ncharlie,35" scanner := bufio.NewScanner(strings.NewReader(csv)) for scanner.Scan() { parts := strings.Split(scanner.Text(), ",") fmt.Printf("name=%s age=%s\n", parts[0], parts[1]) }}Loading Go runtime (first run only, ~8 MB)…