Description
io.CopyBuffer
is difficult to use appropriately: it has a subtle interaction with ReadFrom
and WriteTo
methods (#16474, #32276) that can result in unintended use of small buffers and/or unintended allocation of large slices.
For the cases that avoid that subtle interaction, io.CopyBuffer
mostly duplicates the functionality of bufio.Reader
, particularly given the (*bufio.Reader).Reset
method (which allows a single bufio.Reader
to wrap multiple io.Reader
instances).
I propose that we deprecate io.CopyBuffer
, and encourage explicit use of bufio.Reader
as a replacement.
In particular, note that if the caller intends to always use the buffer, call sites of the form:
var buf = make([]byte, bufLen)
[…]
n, err = io.CopyBuffer(dst, src, buf)
can be expressed more clearly (and more robustly) as
var srcBuf = bufio.NewReaderSize(nil, bufLen)
[…]
srcBuf.Reset(src)
n, err = io.Copy(dst, srcBuf)
The two cases I've found where the existing behavior of io.CopyBuffer
is always beneficial are:
- when the
src
argument is a*bytes.Reader
(or similario.WriterTo
with an internal[]byte
containing its complete output), or - when the
src
argument is a*strings.Reader
(or similario.WriterTo
with an internalstring
containing the complete output) and thedst
argument implementsio.StringWriter
.
In those cases, io.CopyBuffer
avoids an extra copy without inducing an extra allocation. However, as noted in #16474 (comment), detecting those cases requires much more detailed information than what io.CopyBuffer
currently uses (and what ReaderFrom
and WriterTo
currently provide).
Moreover, if the caller knows that the WriterTo
method, if any, is likely to be more efficient than buffering, it's easy enough to invoke explicitly, with explicit use of buffering on the fallback path:
var srcBuf = bufio.NewReaderSize(nil, bufLen)
wt, ok := src.(io.WriterTo)
if !ok {
srcBuf.Reset(src)
wt = srcBuf
}
n, err = wt.WriteTo(dst)