A better io.Reader.Read signature?

122 views
Skip to first unread message

Tom Payne

unread,
Sep 5, 2019, 6:48:21 PM9/5/19
to golang-nuts
Dave Cheney has written (yet another) excellent blog post on Go, on the subject of API design and caller-controlled allocations:

In this he compares two possible signatures for the io.Reader.Read method:
  func (r *Reader) Read(buf []byte) (int, error)
  func (r *Reader) Read() ([]byte, error)
and makes a very good case for why the first, albeit trickier to use, is the better API. The post also sets up a false dichotomy/straw man argument, comparing only two signatures when there are other alternatives too.

What about the following API:
  func (r *Reader) Read(buf []byte) (result []byte, err error)
If the buf argument is non-nil then buf is used to store the result, len(result) == the int returned by the actual Read(buf []byte) (int, error) method.
If the buf argument is nil then a new []byte is created and returned, i.e. the allocation is done by Read and the method is as easy to use as Read() ([]byte, error).
The semantics of the error return remain the same (i.e. Read might return both some bytes read and an error), but that is orthogonal to the memory allocation.
An example of this pattern is here.

Given that slices are small and cheap to copy (a pointer to the data, a length, and a capacity) what are the downsides to using
  func (r *Reader) Read(buf []byte) (result []byte, err error)
as a method?

I am not suggesting that we change io.Reader (that would break everything!), just looking for input into good Go API design.

This would be a comment on Dave's fantastic blog, but the blog does not have comment functionality, so I'm asking here, and I know that Dave lurks here too.

Cheers,
Tom

Michal Strba

unread,
Sep 5, 2019, 7:29:18 PM9/5/19
to Tom Payne, golang-nuts
Your proposed signature has a little problem and that is that when calling

    result, err := reader.Read(nil)

the Read method doesn’t know how many bytes it should read.

The incoming buffer parameter actually serves two purposes. First is to provide a place for storage. The second is to tell how many bytes should be read.

If you want to read all the bytes from a reader, you can use 

If you want to read N bytes into a freshly allocated buffer, you can make a helper function:

    func ReadNBytes(r io.Reader, n int) (result []byte, err error) {
        result = make([]byte, n)
        n, err := r.Read(result)
        return result[:n], err
    }

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/8ffb9828-c6e2-47de-bca1-ef6988f081e8%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages