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

  1. Check reader.Len() < reader.Size() (or track a 'hasRead' flag) before calling UnreadByte to confirm a byte has been consumed.
  2. Ensure exactly one Read/ReadByte occurs between each UnreadByte call.
  3. Restructure the parser so it does not attempt pushback at offset 0 — handle the start-of-input case explicitly.
  4. 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

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


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