iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…