Implementing MultiBytes: Converting [][]byte to io.Reader in Go
This article demonstrates how to implement a MultiBytes struct in Go that converts a [][]byte slice into an io.Reader (and io.Writer), detailing the design, method implementations, and example usage with full source code.
The author was prompted by a question in an ekit project group about converting a [][]byte slice to an io.Reader . Remembering a previous hand‑rolled version of io.MultiReader , they decided to create a new implementation called MultiBytes and add unit tests.
Design : The core data structure is a struct with three fields – data [][]byte to store the nested slices, index int to track the outer slice currently being read, and pos int to track the position inside the current inner slice.
type MultiBytes struct {
data [][]byte // nested slice storing the bytes
index int // outer slice index
pos int // position inside data[index]
}The public API consists of a constructor NewMultiBytes , a Read method that satisfies io.Reader , and a Write method that satisfies io.Writer . The constructor simply stores the provided data and leaves the indices at zero.
// NewMultiBytes constructs a MultiBytes
func NewMultiBytes(data [][]byte) *MultiBytes {
return &MultiBytes{data: data}
}The Read implementation iterates over the nested slices, copying bytes into the supplied buffer p . It handles empty buffers, end‑of‑data (returning io.EOF ), and the case where an inner slice is empty.
// Read implements io.Reader, reading from data into p
func (b *MultiBytes) Read(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
if b.index >= len(b.data) {
return 0, io.EOF
}
n := 0
for n < len(p) {
if b.pos >= len(b.data[b.index]) {
b.index++
b.pos = 0
if b.index >= len(b.data) {
break
}
}
bytes := b.data[b.index]
cnt := copy(p[n:], bytes[b.pos:])
b.pos += cnt
n += cnt
}
if n == 0 {
return 0, io.EOF
}
return n, nil
}The Write method appends a copy of the incoming byte slice to b.data , ensuring that later modifications of the original slice do not affect the stored data.
// Write implements io.Writer, appending data
func (b *MultiBytes) Write(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
clone := make([]byte, len(p))
copy(clone, p)
b.data = append(b.data, clone)
return len(p), nil
}Compile‑time assertions verify that *MultiBytes satisfies both io.Reader and io.Writer interfaces:
var _ io.Reader = (*MultiBytes)(nil)
var _ io.Writer = (*MultiBytes)(nil)An example program creates a MultiBytes from a slice containing "Hello, World!", writes a Chinese string to it, reads the combined data into a buffer, and prints the result:
package main
import (
"fmt"
"github.com/jianghushinian/blog-go-example/iox"
)
func main() {
mb := iox.NewMultiBytes([][]byte{[]byte("Hello, World!\n")})
_, _ = mb.Write([]byte("你好,世界!"))
p := make([]byte, 32)
_, _ = mb.Read(p)
fmt.Println(string(p))
}Running the program prints:
$ go run examples/multi_bytes.go
Hello, World!
你好,世界!In summary, the article provides a concise, functional implementation that turns a two‑dimensional byte slice into an io.Reader (and io.Writer ), complete with constructor, read/write logic, interface checks, and a runnable example.
Go Programming World
Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.