{"record":{"id":"9be16d5458cfed5c","repo":"zed-industries/zed","slug":"byte-index-is-not-a-char-boundary-it-is-inside","errorCode":null,"errorMessage":"byte index {} is not a char boundary; it is inside {:?} (bytes {:?})","messagePattern":"byte index (.+?) is not a char boundary; it is inside (.+?) \\(bytes (.+?)\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/rope/src/chunk.rs","lineNumber":751,"sourceCode":"\n#[cold]\n#[inline(never)]\n#[track_caller]\nfn panic_char_boundary(text: &str, offset: usize) -> ! {\n    if offset > text.len() {\n        panic!(\n            \"byte index {} is out of bounds of `{:?}` (length: {})\",\n            offset,\n            text,\n            text.len()\n        );\n    }\n    // find the character\n    let char_start = text.floor_char_boundary(offset);\n    // `char_start` must be less than len and a char boundary\n    let ch = text.get(char_start..).unwrap().chars().next().unwrap();\n    let char_range = char_start..char_start + ch.len_utf8();\n    panic!(\n        \"byte index {} is not a char boundary; it is inside {:?} (bytes {:?})\",\n        offset, ch, char_range,\n    );\n}\n\n#[cold]\n#[inline(never)]\n#[track_caller]\nfn log_err_char_boundary(text: &str, offset: usize) {\n    if offset >= text.len() {\n        log::error!(\n            \"byte index {} is out of bounds of `{:?}` (length: {})\",\n            offset,\n            text,\n            text.len()\n        );\n        return;\n    }","sourceCodeStart":733,"sourceCodeEnd":769,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/rope/src/chunk.rs#L733-L769","documentation":"Rope chunks enforce UTF-8 boundary rules identical to str slicing: a byte offset used as a boundary must not fall inside a multi-byte character. panic_char_boundary's second branch reports exactly which character the offset splits ('byte index ... is not a char boundary; it is inside ... (bytes a..b)'). It fires when byte arithmetic lands mid-codepoint while slicing or iterating rope text.","triggerScenarios":"Calling rope chunk APIs that require char boundaries with an offset inside a multi-byte UTF-8 sequence: advancing by fixed byte steps over non-ASCII text, offsets derived from substring byte lengths in another encoding, or point/UTF-16 conversions that assume one byte per character.","commonSituations":"Completion or formatting code that advances by fixed byte amounts over mixed-language text; CJK/emoji content; converting LSP UTF-16 positions to byte offsets with naive math; width-limited truncation by byte count.","solutions":["Snap the offset to a boundary before use: step backwards while the byte is a UTF-8 continuation (floor_char_boundary), or forward to the next boundary","Derive offsets from char/grapheme-aware APIs (char_indices, to_offset from a Point/Anchor) instead of raw byte arithmetic","For LSP UTF-16 positions, use a dedicated utf16-to-byte-offset converter, never direct byte math","Add a debug check that offsets passed to slicing APIs satisfy is_char_boundary"],"exampleFix":"// before: fixed byte step may land inside a multi-byte character\nlet cut = start + 20;\nlet left = rope.byte_slice(..cut);\n\n// after: snap down to the enclosing char boundary\nfn floor_char_boundary(bytes: &[u8], mut i: usize) -> usize {\n    i = i.min(bytes.len());\n    while i > 0 && (bytes[i] & 0xC0) == 0x80 {\n        i -= 1;\n    }\n    i\n}\nlet cut = floor_char_boundary(&bytes, start + 20);\nlet left = rope.byte_slice(..cut);","handlingStrategy":"validation","validationCode":"fn floor_char_boundary(bytes: &[u8], mut i: usize) -> usize {\n    i = i.min(bytes.len());\n    while i > 0 && (bytes[i] & 0xC0) == 0x80 {\n        i -= 1;\n    }\n    i\n}\n\n// snap before slicing\nlet cut = floor_char_boundary(&bytes, desired_offset);","typeGuard":"fn is_valid_boundary(text: &str, offset: usize) -> bool {\n    offset <= text.len() && text.is_char_boundary(offset)\n}","tryCatchPattern":null,"preventionTips":["Assume all user text contains multi-byte characters; never do raw byte arithmetic on it","Snap offsets to char boundaries before any slicing call","Convert LSP UTF-16 positions with dedicated conversion helpers, not byte math","Fuzz with unicode-heavy corpora (CJK, emoji, combining marks) to catch boundary bugs early"],"tags":["rope","utf-8","char-boundary","text","panic"],"backgroundTag":"utf8-char-boundary","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}