{"record":{"id":"52320f4ef77e9307","repo":"golang/go","slug":"bytes-reader-unreadrune-at-beginning-of-slice","errorCode":null,"errorMessage":"bytes.Reader.UnreadRune: at beginning of slice","messagePattern":"bytes\\.Reader\\.UnreadRune: at beginning of slice","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/bytes/reader.go","lineNumber":105,"sourceCode":"func (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:])\n\tr.i += int64(size)\n\treturn\n}\n\n// UnreadRune complements [Reader.ReadRune] in implementing the [io.RuneScanner] interface.\nfunc (r *Reader) UnreadRune() error {\n\tif r.i <= 0 {\n\t\treturn errors.New(\"bytes.Reader.UnreadRune: at beginning of slice\")\n\t}\n\tif r.prevRune < 0 {\n\t\treturn errors.New(\"bytes.Reader.UnreadRune: previous operation was not ReadRune\")\n\t}\n\tr.i = int64(r.prevRune)\n\tr.prevRune = -1\n\treturn nil\n}\n\n// Seek implements the [io.Seeker] interface.\nfunc (r *Reader) Seek(offset int64, whence int) (int64, error) {\n\tr.prevRune = -1\n\tvar abs int64\n\tswitch whence {\n\tcase io.SeekStart:\n\t\tabs = offset\n\tcase io.SeekCurrent:\n\t\tabs = r.i + offset","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/bytes/reader.go#L87-L123","documentation":"bytes.Reader.UnreadRune returns this error when the current position r.i is 0 (reader.go:104-106). Even if a ReadRune occurred earlier, the Reader's position-based check fires first: with no position to rewind to, the unread cannot proceed. This is distinct from the 'previous operation was not ReadRune' check, which fires when position > 0 but prevRune < 0.","triggerScenarios":"Triggered by reader.UnreadRune() when r.i <= 0 (reader.go:104). Occurs at the start of the slice, after Seek to 0, after Reset, or after consuming then pushing back to the origin.","commonSituations":"A rune-level tokenizer that calls UnreadRune unconditionally at the top of its loop. Calling UnreadRune at the very start of input before any ReadRune. Position state corrupted by an interleaving Seek(0).","solutions":["Confirm the reader is not at position 0 before calling UnreadRune (check r.Len() < r.Size() or track read state).","Ensure a successful ReadRune precedes each UnreadRune with no intervening Seek/Reset.","Handle the start-of-input case in the parser without attempting a pushback.","Use ReadRune/UnreadRune in tightly paired local blocks so the invariant is obvious."],"exampleFix":"// before — UnreadRune at position 0\nr := bytes.NewReader(data)\nr.UnreadRune() // error: at beginning of slice\n\n// after — read a rune first, then unread conditionally\n_, _, err := r.ReadRune()\nif err == nil && pushBack {\n    r.UnreadRune()\n}","handlingStrategy":"validation","validationCode":"// Only UnreadRune when not at the beginning and after a ReadRune.\nif r.Len() < int(r.Size()) { // not at start\n    if err := r.UnreadRune(); err != nil { return err }\n}","typeGuard":null,"tryCatchPattern":"if err := r.UnreadRune(); err != nil {\n    if strings.Contains(err.Error(), \"at beginning of slice\") {\n        // no rune to push back\n    } else { return err }\n}","preventionTips":["Check the reader is not at position 0 before UnreadRune.","Ensure ReadRune precedes UnreadRune with no intervening Seek/Reset.","Handle start-of-input without attempting pushback."],"tags":["go","bytes","reader","unread","rune","validation"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}