io / os
Go io package: Reader, Writer, Closer interfaces. The small contracts that make pipes, files, network composable.
Go — io package
EXAMPLE
// ===== The core interfaces =====
package io
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type Closer interface { Close() error }
type ReadWriter interface { Reader; Writer }
type ReadCloser interface { Reader; Closer }
// ... and so on
// ===== Usage =====
import (
"io"
"os"
"strings"
)
// Copy from src to dst:
n, err := io.Copy(os.Stdout, strings.NewReader("hello\n"))
// Read all:
data, err := io.ReadAll(reader)
// Limit how much you read (avoid OOM on attacker input):
limited := io.LimitReader(reader, 1<<20) // 1 MiB cap
// ===== Combining =====
// MultiReader: concatenate multiple readers
r := io.MultiReader(r1, r2, r3)
// MultiWriter: write to multiple writers (fan-out)
w := io.MultiWriter(os.Stdout, logFile)
// TeeReader: write everything read to a writer (great for hashing while reading)
import "crypto/sha256"
h := sha256.New()
r := io.TeeReader(file, h)
io.Copy(io.Discard, r)
fmt.Printf("%x\n", h.Sum(nil))
// Pipe: synchronous in-memory pipe
pr, pw := io.Pipe()
go func() {
defer pw.Close()
fmt.Fprintln(pw, "hello from writer")
}()
io.Copy(os.Stdout, pr)
// ===== bufio for buffered + line reading =====
import "bufio"
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
fmt.Println("got:", line)
}
if err := scanner.Err(); err != nil { ... }
// Default token size is 64K; for longer lines:
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
// Buffered writer:
w := bufio.NewWriter(os.Stdout)
fmt.Fprintln(w, "hello")
w.Flush() // do not forget!
// ===== Close hygiene =====
f, err := os.Open("a.txt")
if err != nil { return err }
defer f.Close()
// io.Copy(...)
// For writers, errors from Close matter (final flush). Capture:
err = f.Close()
// ===== Common idioms =====
// Read a whole file:
data, err := os.ReadFile("a.txt") // helper for small files
// Stream-copy a big file:
src, _ := os.Open("big.bin")
defer src.Close()
dst, _ := os.Create("copy.bin")
defer dst.Close()
io.Copy(dst, src)
// Read first 16 bytes:
buf := make([]byte, 16)
_, err = io.ReadFull(src, buf)
// ===== Errors =====
// io.EOF end of stream (often expected)
// io.ErrUnexpectedEOF partial read
// io.ErrClosedPipe write to closed pipe
// ===== Patterns to internalise =====
// - Reader / Writer interfaces compose; anything can plug in
// - LimitReader on user input to bound memory
// - TeeReader for hash-while-stream
// - defer Close() but ALSO capture Close error on writers
// ===== Pitfalls =====
// - Forgetting bufio.Writer.Flush()
// - io.Copy on a slow reader -> blocks indefinitely; pair with context cancellation
// - Reading attacker input without LimitReader -> OOM
// - Closing twice (some readers tolerate; many do not)
Why it matters
io is the package that ties all data movement together in Go. Reader, Writer, Closer interfaces let files, network, in-memory buffers, hashes all compose with io.Copy, MultiReader, TeeReader. Pair with bufio for line-oriented reads + writes and LimitReader for untrusted input.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
data, err := os.ReadFile("input.txt")
if err != nil { log.Fatal(err) }
fmt.Println(string(data))
Try it Yourself »
Discussion
Loading…