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
- Guard with the live unread length: if n >= 0 && n <= b.Len() { b.Truncate(n) } else { /* handle */ }.
- Recompute n from b.Len() (the unread count) immediately before the call, not from a length captured earlier.
- 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).
- 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
- Always derive n from b.Len() (the unread length), never from the backing slice length or a stale counter.
- Clamp computed n to [0, b.Len()] before calling Truncate.
- Use b.Reset() when you want to discard everything; it skips the range check.
- Avoid interleaving manual b.buf slicing with the Buffer API, which desynchronizes b.off and b.Len().
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
- bytes.Buffer.Grow: negative count
- bytes.Buffer: too large
- bytes.Buffer: UnreadRune: previous operation was not a succe
- bytes.Buffer: UnreadByte: previous operation was not a succe
- bytes: negative Repeat count
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/5d60966a6a46c6ec.
Report an issue: GitHub.