Introduction: Why You Might Need to Turn Bytes into a Stream?
\n\nImagine this: you've written a great service in Go that receives data from an external API. The response comes as a byte array ([]byte). That's fine, but here's the catch — the next function in your pipeline expects an io.Reader interface, not a byte slice. What to do? Panic? Write a custom solution from scratch? No! Just remember bytes.NewReader.
This question is one of the most popular on Stack Overflow (thousands of views!). And for good reason. In Go, the io.Reader concept is the foundation for working with data streams: files, network connections, HTTP responses, and, of course, bytes in memory. If you're a beginner Go developer, sooner or later you'll hit this wall. Let's figure out how to get around it elegantly and efficiently.
In this article, we won't just give you the dry command "use bytes.NewReader". We'll break down why it works, what alternatives exist, and show real-life examples. Let's go!
Theory: What is io.Reader and How Does It Work?
\n\nBefore converting, let's recall what io.Reader is. It's an interface defined in the Go standard library:
type Reader interface {\n Read(p []byte) (n int, err error)\n}\n\nIt's brilliantly simple. Any structure that has a Read(p []byte) (int, error) method automatically satisfies the io.Reader interface. This allows you to write universal code for reading data from any source: a file, a socket, a string, a byte slice.
Why is this cool? Because you can write one function that parses JSON, XML, or simply copies data, without caring where it came from. You only need an io.Reader. It's a stream abstraction.
But what if you already have data in memory (e.g., []byte)? You need an object that "pretends" to be a stream but reads from your slice. This is where bytes.NewReader comes into play.
Main Method: bytes.NewReader
\n\nThis is the simplest and most correct approach. The bytes.NewReader function takes a []byte and returns a pointer to a bytes.Reader struct, which implements io.Reader (as well as io.ReadSeeker, io.WriterTo, and other useful interfaces).
Example 1: Basic Usage
\n\npackage main\n\nimport (\n "bytes"\n "fmt"\n "io"\n "log"\n)\n\nfunc main() {\n // Our original byte slice\n data := []byte("Hello, world! These are bytes.")\n\n // Create a Reader from the byte slice\n reader := bytes.NewReader(data)\n\n // Read data from the reader into a buffer\n buf := make([]byte, 10) // read 10 bytes at a time\n for {\n n, err := reader.Read(buf)\n if err != nil {\n if err == io.EOF {\n break // finished reading\n }\n log.Fatal(err)\n }\n fmt.Printf("Read %d bytes: %s\\n", n, buf[:n])\n }\n // Output:\n // Read 10 bytes: Hello, wor\n // Read 10 bytes: ld! These \n // Read 6 bytes: are byt\n // Read 1 bytes: es.\n}\n\nNotice: bytes.Reader behaves like a real stream. When the data runs out, it returns io.EOF. It's all fair.
Example 2: Using with io.Copy
\n\nThe most common scenario is copying bytes somewhere (to a file, to an HTTP response). io.Copy is perfect for this:
package main\n\nimport (\n "bytes"\n "fmt"\n "io"\n "os"\n)\n\nfunc main() {\n data := []byte("Saving this data to a file.")\n reader := bytes.NewReader(data)\n\n // Create a file\n file, err := os.Create("output.txt")\n if err != nil {\n panic(err)\n }\n defer file.Close()\n\n // Copy from reader to file\n