bytes.Buffer
is a tremendously useful type, but it’s a bit large1.
% sizeof -p bytes Buffer Buffer 112
… and that is just the overhead, we haven’t put any data into the buffer yet.
This Friday’s2 challenge is to write a replacement for bytes.Buffer
that implements io.ReadWriter
and allows the caller to discover the length and capacity of the buffer.
The smallest and most creative solution wins fame, adoration, and a first run gb
sticker.
Rules
- The code must continue to be correctly formatted.
- Points will be deducted for arguing with the judge (me).
- Everything you need to win this challenge is in the description; think laterally.
Answers
As I hoped, most readers quickly figured out a good way to save a few lines was to declare Read
and Write
methods on a []byte
, not a struct
. This would lead to some small complications dereferencing the value rather than treating it as a struct with a buf []byte
field, but everyone seemed to figure that out, which is good, as these are useful skills to have in your Go toolbelt.
A few readers also spotted the deliberate loophole I left in the wording of the question around obtaining the length and the capacity of the buffer. Declaring a new type with an underlying type of a slice gives you access to the len
and cap
, so finding the length of a slice requires no additional methods on the type.
type Buffer []byte func main() { var b Buffer b.Write([]byte("howdy") fmt.Println(len(b)) }
Thus, the core of this challenge was to define a new slice type that had Read
and Write
methods, which would end up taking an overhead of 3 machine words, 24 bytes on 64bit platforms, 12 on 32bit.
One nice property of this arrangement is that if you already have a []byte
slice, you can convert it into a Buffer
and consume zero additional storage, as you are effectively replacing the 3 words that described the []byte
with 3 words which describe your new slice type.
s := []byte{0x01, 0x02, 0x03} buf := Buffer(s)
However, as usually happens with these quizzes, a solution arrives that wipes the smug smile from my face,
@davecheney two words: https://t.co/EiVBpr70li
— Kevin Gillette (@kevingillette) June 5, 2015
Kevin, I take my imaginary hat off to you, Sir.
For the record, here was the solution I came up with last night. It is longer than I hoped it would be because of the odd contract that the standard library bytes.Buffer
tests require. I think a more liberal reading of the io.Reader
contract would result in a smaller entry.
// A Buffer is a variable-sized buffer of bytes with Read and Write // methods. The zero value for Buffer is an empty buffer ready to use. type Buffer []byte // Write writes len(p) bytes from p to the Buffer. func (b *Buffer) Write(p []byte) (int, error) { *b = append(*b, p...) return len(p), nil } // Read reads up to len(p) bytes into p from the Buffer. func (b *Buffer) Read(p []byte) (int, error) { if len(p) == 0 { return 0, nil } if len(*b) == 0 { return 0, io.EOF } n := copy(p, *b) *b = (*b)[n:] return n, nil }
So, prizes and glory to @rf, Ben Lubar, and @kevingillette, with special mentions to Egon Elbre, and Dan Kortschak and Douglas Clark from G+. Some of you were more correct than others, but you were all very quick, and that’s got to count for something. I’ll be in touch with your prize.
If a reader wants to debate their solution, and possibly best ours, consider this an open challenge.
- Where do you get the
sizeof
program ? Why, from Russ Cox of course, godoc.org/rsc.io/sizeof (oops, it looks like this doesn’t work with Go 1.4.2, better use tip, or try this online version) - I’m sorry if it isn’t Friday where you live. I can’t help it if Australians live in the future.