Standard library patterns · 3 MIN READ

Borrow bytes briefly; copy before their owner changes

Trace bytes.Buffer and Scanner views, then make retained results independent at a lifetime boundary.

A byte slice can be a borrowed window rather than an owned result. bytes.Buffer.Bytes exposes unread storage, and Scanner.Bytes exposes a token with a limited lifetime. The type []byte does not express that lifetime. Draw the owner, the view and the next mutation before deciding whether to copy.

Byte ownership · A MAP OF THE IDEA

  1. Buffer owns storage
  2. Bytes borrows a view
  3. Clone retained result
  4. Owner may reset

Useful when

  • Borrow for immediate synchronous work
  • Copy only at a lifetime boundary
  • Document validity in the API

Watch out

  • Clip does not copy
  • Sending a slice does not freeze it
  • Reset is not secret erasure

Implementation & operation cost: One deliberate copy buys independent result lifetime. Measure the boundary rather than guessing from allocation count.

01

One owner, one temporary view

Draw a row of bytes for the buffer, then a window labeled view above it. Bytes does not create a separate row. While the borrow is valid, changing an element through the view changes the buffer. A separate copy belongs on a second row with no shared storage.

go
package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buffer bytes.Buffer
    buffer.WriteString("alpha")
    view := buffer.Bytes()
    owned := bytes.Clone(view)
    view[0] = 'A'
    fmt.Println(buffer.String())
    buffer.Reset()
    buffer.WriteString("omega")
    fmt.Println(string(owned))
}
Read the code

The output is Alpha, then alpha. The first print demonstrates aliasing before reset. bytes.Clone creates the owned result before the owner changes. The program deliberately never inspects view after reset: its old contents are not a valid result to rely on.

02

The lifetime timeline

Follow this order: obtain a view, consume it synchronously, then mutate or release the owner. If you must return, cache or enqueue the result beyond that boundary, copy the bytes or transfer the owner explicitly. Assigning the slice to another variable copies its descriptor and preserves sharing. Clip also keeps the existing elements shared.

03

Scanner tokens need the same decision

Scanner.Bytes may expose storage reused by the next Scan. A token consumed immediately can remain borrowed. A token saved in a collection needs its own bytes. Check Scanner.Err after the loop as a separate requirement; correct ownership does not make input parsing infallible.

go
package main

import (
    "bufio"
    "bytes"
    "fmt"
    "strings"
)

func main() {
    scanner := bufio.NewScanner(strings.NewReader("red\nblue\n"))
    var saved [][]byte
    for scanner.Scan() {
        saved = append(saved, bytes.Clone(scanner.Bytes()))
    }
    if err := scanner.Err(); err != nil {
        panic(err)
    }
    for _, token := range saved {
        fmt.Println(string(token))
    }
}
Read the code

Each iteration copies the current token before the next Scan can replace it. The saved values therefore print red and blue. Without a copy, storing the slice descriptors would not establish a durable token contract. The final error check handles scanning failures independently.

04

Asynchronous work crosses the boundary

A successful channel send transfers the slice descriptor, not an immutable snapshot. The producer must stop using the storage until consumption finishes, or send an owned copy. For cancellation, specify who owns a buffer when enqueueing fails and who releases it after a successful handoff.

05

Reset, retention and tests

Reset prepares a buffer for reuse and can retain its allocation. It does not guarantee erasure of sensitive bytes. Test that an owned result remains unchanged after later buffer operations. Avoid tests that require a particular stale value from an invalid view, and measure retained memory when large buffers live much longer than their input.

TAKE THIS WITH YOU

A borrowed result is safe only within its documented lifetime. Draw the next mutation or release, then copy before that boundary when the caller needs a durable value. Test independence of valid results rather than the contents of invalid views.

Check your understanding

A result must survive bytes.Buffer.Reset and later writes. What establishes independent ownership?

Go to the source

Progress stays in this browser. No account needed.