Golang io.ReadCloser: Reader+Closer, NopCloser, and read to string

io.ReadCloser golang and golang readcloser: embed io.Reader and io.Closer, io.NopCloser / nopcloser for Readers without real Close, golang io reader examples, and io.readcloser to string with io.ReadAll plus defer Close.

Published

Updated

Read time 3 min read

Reviewed byDeepak Prasad

Golang io.ReadCloser: Reader+Closer, NopCloser, and read to string

Searches such as golang readcloser, io.readcloser golang, go io.readcloser, io readcloser, io.readcloser to string, golang readcloser to string, io.nopcloser, nopcloser, and golang io reader example all point at the same small idea: io.ReadCloser combines io.Reader with io.Closer, and the standard library gives you io.NopCloser when you only have a reader but something expects a closer. This article shows the interface, io.NopCloser as a golang io reader example bridge, a hand-rolled ReadCloser, and the common readcloser-to-string pattern with io.ReadAll.

Tested with Go 1.24 on Linux.


What is io.ReadCloser?

io.ReadCloser embeds Reader and Closer:

go
type ReadCloser interface {
	Reader
	Closer
}

Callers can read until EOF or an error, then call Close() to release sockets, file descriptors, or other resources. Many APIs return a ReadCloser even when the underlying data lives in memory so the same contract works everywhere.


io.NopCloser (nopcloser)

If you only have an io.Reader but a function needs an io.ReadCloser, wrap it with io.NopCloser (available since Go 1.16). The returned value’s Close does nothing useful; it exists so the static type matches ReadCloser. Older tutorials mention ioutil.NopCloser; ioutil is deprecated for this—use io.NopCloser in new code.

go
package main

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

func main() {
	rc := io.NopCloser(strings.NewReader("Hello GoLinuxCloud Members!"))
	fmt.Printf("Type of readCloser is %T\n", rc)

	buf := new(bytes.Buffer)
	n, err := buf.ReadFrom(rc)
	if err != nil {
		panic(err)
	}
	_ = rc.Close()

	fmt.Printf("Read: %d bytes, content is: %q\n", n, buf.String())
}
Output

On Go 1.24 you will often see the concrete type name io.nopCloserWriterTo because strings.Reader implements io.WriterTo and the wrapper forwards that optimization. For printing types in general, see print type in Go.


Implementing ReadCloser yourself

Any type that already embeds or wraps something with Read can add Close. Here ClosingBuffer embeds *bytes.Buffer (which provides Read) and defines Close:

go
package main

import (
	"bytes"
	"fmt"
	"io"
)

type ClosingBuffer struct {
	*bytes.Buffer
}

func (cb *ClosingBuffer) Close() error {
	return nil
}

func main() {
	cb := &ClosingBuffer{bytes.NewBufferString("Hi GoCloudMember!")}
	var rc io.ReadCloser = cb

	buf := new(bytes.Buffer)
	n, err := buf.ReadFrom(rc)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Read: %d bytes, content is: %q\n", n, buf.String())
	fmt.Printf("Type of cb is %T\n", cb)
	fmt.Printf("Type of rc is %T\n", rc)
	_ = rc.Close()
}
Output

Running it prints the read line with Hi GoCloudMember!, then two lines showing *main.ClosingBuffer for both cb and rc.


io.ReadCloser to string (io.ReadAll)

For io.readcloser to string and golang readcloser to string, read the full body with io.ReadAll (replaces the old ioutil.ReadAll pattern), convert with string(b), and defer resp.Body.Close() on HTTP responses so the connection can be reused. Never call ReadAll and forget Close on a live http.Response.

The program below uses httptest so one go run exercises a JSON body without a second manual server:

go
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httptest"
)

func main() {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]string{
			"message": "Hello GoLinuxCloud Members!",
			"time":    "2022-10-26",
		})
	}))
	defer srv.Close()

	resp, err := http.Get(srv.URL)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	fmt.Printf("Body type: %T\n", resp.Body)

	b, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))
}
text
Body type: *http.bodyEOFSignal
{"message":"Hello GoLinuxCloud Members!","time":"2022-10-26"}

For broader HTTP client patterns, see HTTP in Go. For small inline handlers, anonymous functions are often enough.


Summary

io.ReadCloser golang code paths all come back to read-then-close: the interface is Reader plus Closer, io.NopCloser upgrades a bare Reader to a ReadCloser when close is a no-op, and golang readcloser to string means io.ReadAll then string(bytes), with Close deferred on real transports like http.Response.Body. Knowing nopcloser and the io.nopCloserWriterTo concrete type name helps when you debug types or follow stack traces.


References


Frequently Asked Questions

1. What is io.ReadCloser in golang?

io.ReadCloser is an interface that embeds io.Reader and io.Closer, so values support Read(p []byte) (n int, err error) and Close() error. It is the usual type for bodies and streams that must be fully read then released, such as http.Response.Body.

2. What is io.NopCloser or nopcloser in Go?

Since Go 1.16, io.NopCloser wraps an io.Reader in a ReadCloser whose Close is a no-op. Use it when an API requires ReadCloser but you only have an in-memory Reader like strings.Reader. Prefer io.NopCloser; do not use deprecated ioutil.NopCloser in new code.

3. How do I convert io.ReadCloser to string or golang readcloser to string?

Read the whole stream with io.ReadAll(r) into a byte slice, then use string(b). Always defer r.Close() (or Close after ReadAll) when the closer owns resources, for example HTTP response bodies.

4. Why does fmt print io.nopCloserWriterTo for my NopCloser variable?

The concrete wrapper type is unexported; when the underlying Reader implements io.WriterTo, the wrapper may also implement WriterTo by forwarding calls. The important part for callers is that it still satisfies io.ReadCloser.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …