1. go
  2. /standard library
  3. /io

Working with IO Operations in Go

Go's IO operations are built around two main interfaces: io.Reader and io.Writer. Understanding these interfaces and their implementations is crucial for effective data handling in Go.

Core IO Interfaces

The Reader Interface

type Reader interface {
    Read(p []byte) (n int, err error)
}

The Reader interface represents an entity from which you can read data. Common implementations include:

  • Files (os.File)
  • Network connections (net.Conn)
  • Strings (strings.Reader)
  • Bytes (bytes.Reader)

Example usage:

func readData(r io.Reader) error {
    buf := make([]byte, 1024)
    for {
        n, err := r.Read(buf)
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
        // Process buf[:n]
        fmt.Printf("Read %d bytes\n", n)
    }
    return nil
}

The Writer Interface

type Writer interface {
    Write(p []byte) (n int, err error)
}

The Writer interface represents an entity to which you can write data. Common implementations include:

  • Files (os.File)
  • Network connections (net.Conn)
  • Strings (strings.Builder)
  • Bytes (bytes.Buffer)

Example usage:

func writeData(w io.Writer, data []byte) error {
    n, err := w.Write(data)
    if err != nil {
        return err
    }
    fmt.Printf("Wrote %d bytes\n", n)
    return nil
}

Buffered IO

Using bufio Package

The bufio package implements buffered IO, which can improve performance by reducing the number of system calls:

func readLines(r io.Reader) error {
    scanner := bufio.NewScanner(r)
    for scanner.Scan() {
        line := scanner.Text()
        // Process line
        fmt.Println(line)
    }
    return scanner.Err()
}

func writeBuffered(w io.Writer) error {
    bw := bufio.NewWriter(w)
    _, err := bw.WriteString("Hello, World!\n")
    if err != nil {
        return err
    }
    return bw.Flush()
}

IO Utilities

Copy Operations

The io package provides utility functions for copying data:

func copyExample() error {
    src := strings.NewReader("Hello, World!")
    dst := &bytes.Buffer{}
    
    written, err := io.Copy(dst, src)
    if err != nil {
        return err
    }
    fmt.Printf("Copied %d bytes\n", written)
    return nil
}

MultiReader and MultiWriter

Combine multiple readers or writers:

func multiReaderExample() error {
    r1 := strings.NewReader("Hello")
    r2 := strings.NewReader("World")
    mr := io.MultiReader(r1, r2)
    
    // Read from mr will read from r1 then r2
    data, err := io.ReadAll(mr)
    if err != nil {
        return err
    }
    fmt.Printf("Read: %s\n", data)
    return nil
}

Best Practices

  1. Always Close Resources

    file, err := os.Open("file.txt")
    if err != nil {
        return err
    }
    defer file.Close()
    
  2. Use Buffered IO for Performance

    file, err := os.Create("large-file.txt")
    if err != nil {
        return err
    }
    defer file.Close()
    
    writer := bufio.NewWriter(file)
    defer writer.Flush()
    
  3. Handle EOF Correctly

    func readUntilEOF(r io.Reader) error {
        buf := make([]byte, 1024)
        for {
            n, err := r.Read(buf)
            if err == io.EOF {
                break // Expected EOF, not an error
            }
            if err != nil {
                return err // Unexpected error
            }
            // Process buf[:n]
        }
        return nil
    }
    
  4. Use io.ReadAll for Small Files

    func readSmallFile(filename string) ([]byte, error) {
        file, err := os.Open(filename)
        if err != nil {
            return nil, err
        }
        defer file.Close()
        
        return io.ReadAll(file)
    }
    

Common Patterns

Implementing Custom Readers

type limitReader struct {
    r        io.Reader
    remaining int64
}

func (l *limitReader) Read(p []byte) (n int, err error) {
    if l.remaining <= 0 {
        return 0, io.EOF
    }
    if int64(len(p)) > l.remaining {
        p = p[:l.remaining]
    }
    n, err = l.r.Read(p)
    l.remaining -= int64(n)
    return
}

Implementing Custom Writers

type countingWriter struct {
    w     io.Writer
    count int64
}

func (cw *countingWriter) Write(p []byte) (n int, err error) {
    n, err = cw.w.Write(p)
    cw.count += int64(n)
    return
}

Error Handling

Common IO errors and how to handle them:

func handleIOErrors(err error) {
    switch {
    case errors.Is(err, io.EOF):
        fmt.Println("Reached end of file")
    case errors.Is(err, io.ErrShortWrite):
        fmt.Println("Short write occurred")
    case errors.Is(err, io.ErrUnexpectedEOF):
        fmt.Println("Unexpected EOF")
    default:
        fmt.Printf("Unknown error: %v\n", err)
    }
}

Next Steps