Golang - Interfaces - Common Interfaces (io.Reader, io.Writer)

Publish date: 2025-09-26
Tags: <a href="https://programmercave.com/tags/Go/">Go</a>

Introduction

The io.Reader and io.Writer interfaces are arguably two of the most important and widely used interfaces in the entire Go standard library. They are the fundamental building blocks for I/O operations, providing a simple, powerful abstraction for reading from and writing to streams of data.

Analogy:


1. io.Reader

The io.Reader interface has just one method:

type Reader interface {
    // Read reads up to len(p) bytes into p.
    // It returns the number of bytes read (n) and any error encountered.
    // At the end of the stream, it returns n > 0, io.EOF or n = 0, io.EOF.
    Read(p []byte) (n int, err error)
}

What does it do? It provides a single, unified way to read data from a source. That source could be anything:

Because all these different types implement io.Reader, you can write a single function that can process data from any of them without needing to know the details of the underlying source.

Example: A function that counts the bytes from any source.

package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
)

// countBytes takes any io.Reader and returns the number of bytes it could read.
func countBytes(r io.Reader) (int64, error) {
	// io.Copy reads from a Reader and writes to a Writer.
	// ioutil.Discard is a Writer that just throws away the data.
	// This is an efficient way to consume and count a stream.
	return io.Copy(io.Discard, r)
}

func main() {
	// 1. Read from a string
	stringReader := strings.NewReader("hello world")
	count, _ := countBytes(stringReader)
	fmt.Printf("The string has %d bytes.\n", count)

	// 2. Read from an in-memory buffer
	bufferReader := bytes.NewBuffer([]byte{0, 1, 2, 3, 4})
	count, _ := countBytes(bufferReader)
	fmt.Printf("The buffer has %d bytes.\n", count)

	// 3. Read from a file
	file, _ := os.Open("my_file.txt") // (assume this file exists)
	defer file.Close()
	count, _ := countBytes(file)
	fmt.Printf("The file has %d bytes.\n", count)
}

2. io.Writer

The io.Writer interface is the counterpart to io.Reader. It also has just one method:

type Writer interface {
    // Write writes len(p) bytes from p to the underlying data stream.
    // It returns the number of bytes written (n) and any error encountered.
    Write(p []byte) (n int, err error)
}

What does it do? It provides a single, unified way to write data to a destination. That destination could be:

Example: fmt.Fprintf The fmt.Fprintf function is a perfect example. Its first argument is an io.Writer. This allows it to write a formatted string to any destination that implements the Writer interface.

package main

import (
	"bytes"
	"fmt"
	"os"
)

func main() {
	// 1. Write to Standard Output
	fmt.Fprintf(os.Stdout, "Hello, %s!\n", "console")

	// 2. Write to a file
	file, _ := os.Create("output.txt")
	defer file.Close()
	fmt.Fprintf(file, "Hello, %s!\n", "file")

	// 3. Write to an in-memory buffer
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "Hello, %s!\n", "buffer")
	fmt.Println("Buffer contents:", buf.String())
}

Combining Them: io.Copy

The power of these interfaces is most evident when they are combined. The io.Copy function is a prime example:

func Copy(dst Writer, src Reader) (written int64, err error)

io.Copy reads data from a Reader and writes it to a Writer. It is incredibly versatile. You can use it to:

This simple, one-line function replaces complex loops for reading and writing, and it works with any I/O source and destination, all thanks to the power of interfaces.

Tags: <a href="https://programmercave.com/tags/Go/">Go</a>