golang/go · error
bytes.Reader.UnreadByte: at beginning of slice
Error message
bytes.Reader.UnreadByte: at beginning of slice
What it means
bytes.Reader.UnreadByte returns this error when the current read position r.i is already 0 (reader.go:78-80). UnreadByte moves the position back by one byte; if there is no prior byte to return to (position at the very start of the slice), the operation is impossible and the Reader reports it rather than underflowing the index.
Source
Thrown at src/bytes/reader.go:79
}
return
}
// ReadByte implements the [io.ByteReader] interface.
func (r *Reader) ReadByte() (byte, error) {
r.prevRune = -1
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
b := r.s[r.i]
r.i++
return b, nil
}
// UnreadByte complements [Reader.ReadByte] in implementing the [io.ByteScanner] interface.
func (r *Reader) UnreadByte() error {
if r.i <= 0 {
return errors.New("bytes.Reader.UnreadByte: at beginning of slice")
}
r.prevRune = -1
r.i--
return nil
}
// ReadRune implements the [io.RuneReader] interface.
func (r *Reader) ReadRune() (ch rune, size int, err error) {
if r.i >= int64(len(r.s)) {
r.prevRune = -1
return 0, 0, io.EOF
}
r.prevRune = int(r.i)
if c := r.s[r.i]; c < utf8.RuneSelf {
r.i++
return rune(c), 1, nil
}
ch, size = utf8.DecodeRune(r.s[r.i:])View on GitHub (pinned to b6b368adc5)
Solutions
- Check reader.Len() < reader.Size() (or track a 'hasRead' flag) before calling UnreadByte to confirm a byte has been consumed.
- Ensure exactly one Read/ReadByte occurs between each UnreadByte call.
- Restructure the parser so it does not attempt pushback at offset 0 — handle the start-of-input case explicitly.
- If you need to re-examine bytes near the start, use ReadAt(0) instead of pushback.
Example fix
// before — UnreadByte at position 0 fails
r := bytes.NewReader(data)
r.UnreadByte() // error: at beginning of slice
// after — read first, then unread only when a byte was consumed
b, err := r.ReadByte()
if err == nil && shouldPushBack {
r.UnreadByte()
} Defensive patterns
Strategy: validation
Validate before calling
// Only unread when a byte has actually been consumed.
if r.Len() < int(r.Size()) { // something has been read
if err := r.UnreadByte(); err != nil { return err }
} Try / catch
if err := r.UnreadByte(); err != nil {
if strings.Contains(err.Error(), "at beginning of slice") {
// no byte to push back; proceed without unread
} else { return err }
} Prevention
- Do not call UnreadByte at position 0 — check r.Len() < r.Size() first.
- Pair each UnreadByte with exactly one preceding ReadByte.
- Handle start-of-input explicitly rather than attempting pushback.
When it happens
Trigger: Triggered by calling reader.UnreadByte() when r.i <= 0. This happens at the start of the slice, or after a Seek to position 0, or after a Reset — any state where no byte has been consumed yet (or the consumed byte was already pushed back).
Common situations: A tokenizer calling UnreadByte at the very start of input. Calling UnreadByte twice without an intervening Read. Calling UnreadByte after a Seek back to position 0. Loops that speculatively unread on every iteration without checking position.
Related errors
- bytes.Reader.UnreadRune: at beginning of slice
- bytes.Reader.UnreadRune: previous operation was not ReadRune
- bytes.Buffer: UnreadRune: previous operation was not a succe
- bytes.Buffer: UnreadByte: previous operation was not a succe
- bytes.Reader.ReadAt: negative offset
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/98c1aa6e6cbbc2b7.
Report an issue: GitHub.