• English
  • Example: io.NopCloser

    io.NopCloser adapts an io.Reader to an io.ReadCloser by adding a Close method that does nothing.

    It is useful when an API requires an io.ReadCloser, but the underlying reader has no resource that needs closing.

    The important rule is:

    io.NopCloser adapts an interface. It does not manage the underlying resource.

    Quick Example

    package main
    
    import (
    	"fmt"
    	"io"
    	"strings"
    )
    
    func main() {
    	src := strings.NewReader("hello, gobase")
    
    	rc := io.NopCloser(src)
    	defer rc.Close()
    
    	data, err := io.ReadAll(rc)
    	if err != nil {
    		fmt.Println("read failed:", err)
    		return
    	}
    
    	fmt.Println(string(data))
    }

    Output:

    hello, gobase

    rc.Read forwards to src.Read.

    rc.Close() simply returns nil.

    No data is copied or buffered by NopCloser.

    Why It Exists

    Some APIs require an io.ReadCloser even when the underlying data is just an in-memory reader.

    For example, http.Response.Body is an io.ReadCloser:

    resp := &http.Response{
    	StatusCode: 200,
    	Body:       io.NopCloser(strings.NewReader(`{"ok":true}`)),
    }
    
    defer resp.Body.Close()
    
    body, err := io.ReadAll(resp.Body)
    if err != nil {
    	return err
    }
    
    fmt.Println(string(body))

    This is common when constructing HTTP responses for tests, mocks, or custom proxy code.

    For http.Request, io.NopCloser is usually unnecessary. http.NewRequest accepts an io.Reader and handles common in-memory reader types itself.

    Close Does Nothing

    Closing the wrapper does not close the underlying reader.

    src := strings.NewReader("hello")
    rc := io.NopCloser(src)
    
    _ = rc.Close()
    
    data, err := io.ReadAll(src)
    if err != nil {
    	return err
    }
    
    fmt.Println(string(data))

    The underlying reader is still usable.

    This is intentional: NopCloser has no ownership of the resource represented by the reader.

    The Important Trap

    Do not use io.NopCloser to hide a resource that actually needs cleanup.

    For example:

    file, err := os.Open("data.txt")
    if err != nil {
    	return err
    }
    
    rc := io.NopCloser(file)
    defer rc.Close() // Does NOT close file.

    rc.Close() only calls the no-op Close provided by NopCloser.

    The file descriptor remains open unless file.Close() is called separately.

    Use the original io.ReadCloser when the underlying resource needs lifecycle management:

    file, err := os.Open("data.txt")
    if err != nil {
    	return err
    }
    defer file.Close()

    The same principle applies to network connections, response bodies, and other resource-owning readers.

    Common Use

    Use io.NopCloser when:

    • an in-memory reader must satisfy io.ReadCloser
    • constructing an http.Response for a test
    • replacing a request or response body with in-memory data
    • an API requires io.ReadCloser, but there is no resource to close

    Do not use it when:

    • the underlying object owns a file descriptor
    • the underlying object owns a network connection
    • Close has meaningful cleanup semantics
    • the value already implements io.ReadCloser

    Rule of Thumb

    Use io.NopCloser to add a no-op Close, not to suppress a real Close.

    It changes the interface presented to the caller, not the ownership or lifetime of the underlying resource.