golang/go · error

bytes.Buffer: truncation out of range

Error message

bytes.Buffer: truncation out of range

What it means

bytes.Buffer.Truncate(n) discards all but the first n UNREAD bytes of the buffer (the unread portion is len(b.buf) - b.off, exposed as b.Len()). It panics with "bytes.Buffer: truncation out of range" when n < 0 or n > b.Len(). Because Truncate operates on the unread region (past b.off), passing a length computed against the whole backing slice or against a pre-read length will trigger the panic even when the intent looks safe.

Source

Thrown at src/bytes/buffer.go:116

// Cap returns the capacity of the buffer's underlying byte slice, that is, the
// total space allocated for the buffer's data.
func (b *Buffer) Cap() int { return cap(b.buf) }

// Available returns how many bytes are unused in the buffer.
func (b *Buffer) Available() int { return cap(b.buf) - len(b.buf) }

// Truncate discards all but the first n unread bytes from the buffer
// but continues to use the same allocated storage.
// It panics if n is negative or greater than the length of the buffer.
func (b *Buffer) Truncate(n int) {
	if n == 0 {
		b.Reset()
		return
	}
	b.lastRead = opInvalid
	if n < 0 || n > b.Len() {
		panic("bytes.Buffer: truncation out of range")
	}
	b.buf = b.buf[:b.off+n]
}

// Reset resets the buffer to be empty,
// but it retains the underlying storage for use by future writes.
// Reset is the same as [Buffer.Truncate](0).
func (b *Buffer) Reset() {
	b.buf = b.buf[:0]
	b.off = 0
	b.lastRead = opInvalid
}

// tryGrowByReslice is an inlineable version of grow for the fast-case where the
// internal buffer only needs to be resliced.
// It returns the index where bytes should be written and whether it succeeded.
func (b *Buffer) tryGrowByReslice(n int) (int, bool) {
	if l := len(b.buf); n <= cap(b.buf)-l {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Guard with the live unread length: if n >= 0 && n <= b.Len() { b.Truncate(n) } else { /* handle */ }.
  2. Recompute n from b.Len() (the unread count) immediately before the call, not from a length captured earlier.
  3. If the goal is to drop the whole unread buffer, call b.Reset() instead of Truncate(0) (Truncate already special-cases 0 to Reset, but Reset makes intent explicit and avoids the range check entirely).
  4. If you need to keep the whole backing array but reset logical position, prefer Reset over a Truncate derived from stale state.

Example fix

// before
b.Write(data)
b.Read(prefix)
b.Truncate(len(data) - len(prefix)) // len(data) is stale; may exceed b.Len()

// after
b.Write(data)
b.Read(prefix)
n := len(data) - len(prefix)
if n < 0 {
    n = 0
}
if n > b.Len() {
    n = b.Len()
}
b.Truncate(n)
Defensive patterns

Strategy: validation

Validate before calling

// Validate before Truncate: n must be within [0, b.Len()].
func safeTruncate(b *bytes.Buffer, n int) error {
    if n < 0 || n > b.Len() {
        return fmt.Errorf("truncate %d out of range [0,%d]", n, b.Len())
    }
    b.Truncate(n)
    return nil
}

Prevention

When it happens

Trigger: Calling b.Truncate(n) with n negative; calling b.Truncate(n) where n exceeds the current unread length b.Len() (e.g. after bytes have already been consumed by Read/Next); passing len(b.Bytes()) (which is the unread slice length but reflects a stale view) after intermediate mutations; computing n from the original write length rather than from b.Len().

Common situations: Off-by-one when trimming a trailing delimiter or newline (n = len(data) - 1 while data was already partially read); mixing manual b.buf slicing with the Buffer API so b.off and b.Len() drift; using Truncate to implement an "undo last write" where n is taken from a variable updated before Read; streaming parsers that Truncate to a computed offset after consuming a header.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/5d60966a6a46c6ec. Report an issue: GitHub.