I’ve run quite a few times into this issue lately. I have an io.Reader which wraps another io.Reader but does not offer a way to close the inner io.Reader.

One instance of this issue is with the bufio.Reader type. The function below opens the given file, peeks at the first 512 bytes to try to detect the mime type, and then returns an io.ReadCloser to read and then close the file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func Open(name string) (io.ReadCloser, string, error)
    file, err := os.Open("file.pdf")
    if err != nil {
        return nil, "", err
    }

    br := bufio.NewReader(file)
    buf, err := br.Peek(512)
    if err != nil && err != io.EOF {
        return nil, "", err
    }

    mimeType := http.DetectContentType(buf)
    return br, mimeType, nil
}

This code does not compile because bufio.Reader does not implement the io.Closer interface. Returning just an io.Reader would not be ideal because the opened file would get leaked, at least until the garbage collector runs.

There is an elegant pattern to fix this issue. The FuncCloser pattern, as opposed to io.NopCloser allows one to add an arbitrary Close() error function to an io.Reader, turning it into an io.ReadCloser.

The code above could be fixed easily by modifying the return line to:

1
return iox.FuncCloser(br, file.Close), mimeType, nil

More complex closing behavior can also be achieved by using a closure. Imagine that file is actually a temporary file that we must remove once we’re done using it. We can easily close and remove it like so:

1
2
3
4
return iox.FuncCloser(br, func() error {
    file.Close()
    os.Remove(name)
}), mimeType, nil

Implementing this pattern in your project is trivial:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package iox

import "io"

type funcCloser struct {
	io.Reader
	close func() error
}

func (r funcCloser) Close() error {
	return r.close()
}

func FuncCloser(r io.Reader, close func() error) io.ReadCloser {
	return funcCloser{
		Reader: r,
		close:  close,
	}
}

The pattern might be useful for io.Writer/io.WriteCloser as well!