-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
io: should TeeReader return an io.ReadCloser? #27617
Comments
Sorry, but this can't be done in a backwards compatible way. The following code would break:
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
|
@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: |
I don't think your last suggestion is worth adding to the
|
Taken to the logical extreme, though, that pattern would either require compile-time metaprogramming or result in an explosion of ¹ See, for example, this experience report. |
What if there were 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 |
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. |
Per the discussion here, we can't change the current |
The However, do understand that changing the current impl. would break a lot of things tho as the maintainers have said. So just leaving this code here in case anyone needs it. I went over a few ways to do this and ended up with this which I think is pretty minimal and Go-ish (if that's a thing) and doesn't require you to restructure a ton of code. Just throw a type closeSegue struct {
reader io.Reader
closer io.Closer
}
func (r *closeSegue) Read(p []byte) (n int, err error) { return r.reader.Read(p) }
func (r *closeSegue) Close() error { return r.closer.Close() } Here's an example of how I'm using it to "tee" incoming request body to stdout and still ensure that other middlewares down the line can still properly if config.Get(cfg, DebugRequestConfig) {
tee := io.TeeReader(req.Body, debugOut)
req.Body = &closeSegue{reader: tee, closer: req.Body}
}
next.ServeHTTP(resp, req) |
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.
The text was updated successfully, but these errors were encountered: