Skip to content

Commit 3c85eb4

Browse files
committed
so/bytes: zero-copy NewBuffer
1 parent 3ce7d0e commit 3c85eb4

3 files changed

Lines changed: 73 additions & 27 deletions

File tree

so/bytes/buffer.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ const maxInt = int(math.MaxInt64)
3232

3333
// A Buffer is a variable-sized buffer of bytes with [Buffer.Read] and [Buffer.Write] methods.
3434
// The zero value for Buffer is an empty buffer ready to use (with default allocator).
35+
// A Buffer grows as needed when writing data, using the provided allocator.
36+
// The caller is responsible for freeing the buffer's resources
37+
// with [Buffer.Free] when done using it.
3538
type Buffer struct {
3639
a mem.Allocator // memory allocator; nil falls back to default one.
3740
buf []byte // contents are the bytes buf[off : len(buf)]
@@ -384,29 +387,38 @@ func (b *Buffer) ReadString(delim byte) (string, error) {
384387
return String(b.a, slice), err
385388
}
386389

387-
// NewBuffer creates and initializes a new [Buffer] using a copy of buf as its
388-
// initial contents. It is intended to prepare a buffer to read existing data.
390+
// NewBuffer creates and initializes a new [Buffer] using buf as its
391+
// initial contents. The new Buffer takes ownership of buf, and the
392+
// caller should not use buf after this call. NewBuffer is intended to
393+
// prepare a Buffer to read existing data. It can also be used to set
394+
// the initial size of the internal buffer for writing. To do that,
395+
// buf should have the desired capacity but a length of zero.
396+
//
397+
// If buf was allocated with an allocator, the same allocator must be
398+
// passed to NewBuffer so that [Buffer.Free] can release it correctly.
399+
// Do not call [Buffer.Free] if buf was not heap-allocated.
400+
//
401+
// Do not provide a stack-allocated buf if you intend to write to the buffer,
402+
// as the buffer may need to grow and reallocate, which would cause free()
403+
// on a stack pointer. Only use heap-allocated slices in this case.
389404
//
390405
// If the allocator is nil, uses the system allocator.
391-
// The caller is responsible for freeing the buffer's resources
392-
// with [Buffer.Free] when done using it.
393406
func NewBuffer(a mem.Allocator, buf []byte) Buffer {
394-
if buf == nil {
395-
return Buffer{a: a}
396-
}
397-
b := mem.AllocSlice[byte](a, len(buf), cap(buf))
398-
copy(b, buf)
399-
return Buffer{a: a, buf: b}
407+
return Buffer{a: a, buf: buf}
400408
}
401409

402410
// NewBufferString creates and initializes a new [Buffer] using string s as its
403411
// initial contents. It is intended to prepare a buffer to read an existing string.
404412
//
413+
// If s was allocated with an allocator, the same allocator must be
414+
// passed to NewBuffer so that [Buffer.Free] can release it correctly.
415+
// Do not call [Buffer.Free] if s was not heap-allocated.
416+
//
417+
// Do not provide a stack-allocated s if you intend to write to the buffer,
418+
// as the buffer may need to grow and reallocate, which would cause free()
419+
// on a stack pointer. Only use heap-allocated strings in this case.
420+
//
405421
// If the allocator is nil, uses the system allocator.
406-
// The caller is responsible for freeing the buffer's resources
407-
// with [Buffer.Free] when done using it.
408422
func NewBufferString(a mem.Allocator, s string) Buffer {
409-
buf := mem.AllocSlice[byte](a, len(s), len(s))
410-
copy(buf, s)
411-
return Buffer{a: a, buf: buf}
423+
return Buffer{a: a, buf: []byte(s)}
412424
}

testdata/std/bytes/dst/main.c

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,27 @@ int main(void) {
218218
mem_FreeSlice(so_byte, (mem_Allocator){0}, uppered);
219219
}
220220
{
221-
// Buffer.
222-
bytes_Buffer buf = bytes_NewBuffer((mem_Allocator){0}, so_string_bytes(so_str("hello")));
223-
bytes_Buffer_Write(&buf, so_string_bytes(so_str(" world")));
221+
// Buffer (stack-allocated).
222+
bytes_Buffer buf = bytes_NewBuffer((mem_Allocator){0}, so_string_bytes(so_str("hello world")));
223+
if (so_string_ne(bytes_Buffer_String(&buf), so_str("hello world"))) {
224+
so_panic("Buffer Write failed");
225+
}
226+
so_Slice rdbuf = so_make_slice(so_byte, 5, 5);
227+
so_R_int_err _res1 = bytes_Buffer_Read(&buf, rdbuf);
228+
so_int n = _res1.val;
229+
so_Error err = _res1.err;
230+
if (n != 5 || so_string_ne(so_bytes_string(rdbuf), so_str("hello")) || err != NULL) {
231+
so_panic("Buffer Read failed");
232+
}
233+
if (so_string_ne(bytes_Buffer_String(&buf), so_str(" world"))) {
234+
so_panic("Buffer Read did not advance the buffer");
235+
}
236+
}
237+
{
238+
// Buffer (heap-allocated).
239+
bytes_Buffer buf = bytes_NewBuffer((mem_Allocator){0}, (so_Slice){0});
240+
bytes_Buffer_WriteString(&buf, so_str("hello"));
241+
bytes_Buffer_WriteString(&buf, so_str(" world"));
224242
if (so_string_ne(bytes_Buffer_String(&buf), so_str("hello world"))) {
225243
so_panic("Buffer Write failed");
226244
}
@@ -229,9 +247,9 @@ int main(void) {
229247
so_panic("Buffer Grow failed");
230248
}
231249
so_Slice rdbuf = so_make_slice(so_byte, 5, 5);
232-
so_R_int_err _res1 = bytes_Buffer_Read(&buf, rdbuf);
233-
so_int n = _res1.val;
234-
so_Error err = _res1.err;
250+
so_R_int_err _res2 = bytes_Buffer_Read(&buf, rdbuf);
251+
so_int n = _res2.val;
252+
so_Error err = _res2.err;
235253
if (n != 5 || so_string_ne(so_bytes_string(rdbuf), so_str("hello")) || err != NULL) {
236254
so_panic("Buffer Read failed");
237255
}
@@ -247,9 +265,9 @@ int main(void) {
247265
if (bytes_Reader_Len(&r) != so_len(s)) {
248266
so_panic("Reader Len failed");
249267
}
250-
so_R_slice_err _res2 = io_ReadAll((mem_Allocator){0}, (io_Reader){.self = &r, .Read = bytes_Reader_Read});
251-
so_Slice b = _res2.val;
252-
so_Error err = _res2.err;
268+
so_R_slice_err _res3 = io_ReadAll((mem_Allocator){0}, (io_Reader){.self = &r, .Read = bytes_Reader_Read});
269+
so_Slice b = _res3.val;
270+
so_Error err = _res3.err;
253271
if (err != NULL) {
254272
so_panic(err->msg);
255273
}

testdata/std/bytes/src/main.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,25 @@ func main() {
219219
mem.FreeSlice(nil, uppered)
220220
}
221221
{
222-
// Buffer.
223-
buf := bytes.NewBuffer(nil, []byte("hello"))
224-
buf.Write([]byte(" world"))
222+
// Buffer (stack-allocated).
223+
buf := bytes.NewBuffer(nil, []byte("hello world"))
224+
if buf.String() != "hello world" {
225+
panic("Buffer Write failed")
226+
}
227+
rdbuf := make([]byte, 5)
228+
n, err := buf.Read(rdbuf)
229+
if n != 5 || string(rdbuf) != "hello" || err != nil {
230+
panic("Buffer Read failed")
231+
}
232+
if buf.String() != " world" {
233+
panic("Buffer Read did not advance the buffer")
234+
}
235+
}
236+
{
237+
// Buffer (heap-allocated).
238+
buf := bytes.NewBuffer(nil, nil)
239+
buf.WriteString("hello")
240+
buf.WriteString(" world")
225241
if buf.String() != "hello world" {
226242
panic("Buffer Write failed")
227243
}

0 commit comments

Comments
 (0)