Description
The io.Reader.Read
method is notoriously tricky, with lots of docs: https://pkg.go.dev/io#Reader
In particular, it's common for people new to Go to write:
var buf [N]byte
_, err := r.Read(buf[:])
... and think they're reading N bytes. Especially when it almost always works. The testing/iotest
package is easy to miss (and easier to just not use, even when you're aware of it).
I regularly correct that pattern in code reviews when I see others make that mistake, and despite writing Go for over 11 years, I also made that mistake the other day, causing problems today.
The io
package has these helpers that, in addition to doing as documented, also declare the author's intent:
func ReadAll(r Reader) ([]byte, error)
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
func ReadFull(r Reader, buf []byte) (n int, err error)
When the author writes the snippet at top, though, it's hard to know intent. Sometimes it's actually legit to read fewer:
var buf [1500]byte
if n, err := conn.Read(buf[:]); err != nil {
panic(err)
}
processIncomingUDPPacket(buf[:n])
It'd almost be nice to have a new "helper" in the io
package like:
package io
// ReadSome returns r.Read(buf).
//
// It does nothing extra besides declare your intent that you're
// cool with n being < len(buf).
func ReadSome(r io.Reader, buf []byte) (n int, err error) { return r.Read(buf) }
Then all raw io.Reader.Read
calls could be replaced with io.ReadFull
, io.ReadSome
etc, and a raw calls would then stand out as shady, warranting further inspection or annotation.
People who wanted to be really strict could make their static analysis tool(s) of choice even forbid raw Read calls.
/cc @danderson @dsnet