Go: io: should TeeReader return an io.ReadCloser?

Created on 11 Sep 2018  ·  7Comments  ·  Source: golang/go

io.TeeReader can't be used to wrap an io.ReadCloser as that strips the io.Closer part.

io.TeeReader could return an io.ReadCloser though as it could implement the Close method by calling the Close method of the given reader if it is an io.ReadCloser. The question is just what should happen if the given reader is just an io.Reader. In that case I would propose to just do nothing on Close method call as the wrapped io.Reader doesn't need to be closed.

IMHO this shouldn't violate the Go 1 compatibility as io.ReadCloser includes io.Reader and so all existing code should continue to work.

Thoughts? I'm happy to send a pull request.

Go2 NeedsDecision

Most helpful comment

I don't think your last suggestion is worth adding to the io package. It's trivial to do with user code:

type readCloser struct {
    io.Reader
    io.Closer
}

func useTeeReader(rc io.ReadCloser, w io.Writer) {
    tee := io.TeeReader(rc, w)
    teeCloser := readCloser{tee, rc}
    useAndClose(teeCloser)
}

All 7 comments

Sorry, but this can't be done in a backwards compatible way. The following code would break:

var f func(io.Reader, io.Writer) io.Reader = io.TeeReader

I'm also unsure if this is a good idea, even for Go2. It would complicate the API quite a bit, when most users don't care about close errors when using an io.Reader.

TeeReader also seems simple enough, so it should be easy to just copy the code and adapt it to your needs.

@mvdan You make a good point that this would indeed break the Go 1 compatibility. I didn't think about that loophole. sigh

Can we then have an io.TeeReadCloser instead or an io.NewReadCloser to create a ReadCloser from a Reader and Closer? The later could be used like this: io.NewReadCloser(io.TeeReader(r, w), r) to achieve the same as an io.TeeReadCloser.

I don't think your last suggestion is worth adding to the io package. It's trivial to do with user code:

type readCloser struct {
    io.Reader
    io.Closer
}

func useTeeReader(rc io.ReadCloser, w io.Writer) {
    tee := io.TeeReader(rc, w)
    teeCloser := readCloser{tee, rc}
    useAndClose(teeCloser)
}

io.teeReader could in theory implement io.Closer even if we didn't change the signature of the io.TeeReader function.

Taken to the logical extreme, though, that pattern would either require compile-time metaprogramming or result in an explosion of Reader implementations: there are other methods (such as WriteTo) that could/should be supported, and for n orthogonal wrappers we currently need 2ⁿ wrappers.¹

¹ See, for example, this experience report.

What if there were io.AutoCloser(rc io.ReadCloser) io.Reader? Implementation could be this trivial:

type autocloser struct {
    rc io.ReadCloser
}

func (ac autocloser) Read(p []byte) (n int, err error) {
    n, err = ac.rc.Read(p)
    if err != nil {
        _ = ac.rc.Close()
    }
    return
}

@carlmjohnson I don't think the io package should be encouraging that. Closing a reader is more than just reaching EOF or an error. What about the cases where one wants to close before reaching EOF? What about when one wants to read a reader's contents multiple times via io.Seeker?

In those cases, don't use the autocloser? :-) I think reading to io.EOF or first error is a pretty common case, but obviously, there are a lot of times when it doesn't apply and you shouldn't use it.

Was this page helpful?
0 / 5 - 0 ratings