{"record":{"id":"98c1aa6e6cbbc2b7","repo":"golang/go","slug":"bytes-reader-unreadbyte-at-beginning-of-slice","errorCode":null,"errorMessage":"bytes.Reader.UnreadByte: at beginning of slice","messagePattern":"bytes\\.Reader\\.UnreadByte: at beginning of slice","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/bytes/reader.go","lineNumber":79,"sourceCode":"\t}\n\treturn\n}\n\n// ReadByte implements the [io.ByteReader] interface.\nfunc (r *Reader) ReadByte() (byte, error) {\n\tr.prevRune = -1\n\tif r.i >= int64(len(r.s)) {\n\t\treturn 0, io.EOF\n\t}\n\tb := r.s[r.i]\n\tr.i++\n\treturn b, nil\n}\n\n// UnreadByte complements [Reader.ReadByte] in implementing the [io.ByteScanner] interface.\nfunc (r *Reader) UnreadByte() error {\n\tif r.i <= 0 {\n\t\treturn errors.New(\"bytes.Reader.UnreadByte: at beginning of slice\")\n\t}\n\tr.prevRune = -1\n\tr.i--\n\treturn nil\n}\n\n// ReadRune implements the [io.RuneReader] interface.\nfunc (r *Reader) ReadRune() (ch rune, size int, err error) {\n\tif r.i >= int64(len(r.s)) {\n\t\tr.prevRune = -1\n\t\treturn 0, 0, io.EOF\n\t}\n\tr.prevRune = int(r.i)\n\tif c := r.s[r.i]; c < utf8.RuneSelf {\n\t\tr.i++\n\t\treturn rune(c), 1, nil\n\t}\n\tch, size = utf8.DecodeRune(r.s[r.i:])","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/bytes/reader.go#L61-L97","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before — UnreadByte at position 0 fails\nr := bytes.NewReader(data)\nr.UnreadByte() // error: at beginning of slice\n\n// after — read first, then unread only when a byte was consumed\nb, err := r.ReadByte()\nif err == nil && shouldPushBack {\n    r.UnreadByte()\n}","handlingStrategy":"validation","validationCode":"// Only unread when a byte has actually been consumed.\nif r.Len() < int(r.Size()) { // something has been read\n    if err := r.UnreadByte(); err != nil { return err }\n}","typeGuard":null,"tryCatchPattern":"if err := r.UnreadByte(); err != nil {\n    if strings.Contains(err.Error(), \"at beginning of slice\") {\n        // no byte to push back; proceed without unread\n    } else { return err }\n}","preventionTips":["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."],"tags":["go","bytes","reader","unread","validation"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}