zed-industries/zed · error
byte index {} is not a char boundary; it is inside {:?} (byt
Error message
byte index {} is not a char boundary; it is inside {:?} (bytes {:?}) What it means
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.
Source
Thrown at crates/rope/src/chunk.rs:751
#[cold]
#[inline(never)]
#[track_caller]
fn panic_char_boundary(text: &str, offset: usize) -> ! {
if offset > text.len() {
panic!(
"byte index {} is out of bounds of `{:?}` (length: {})",
offset,
text,
text.len()
);
}
// find the character
let char_start = text.floor_char_boundary(offset);
// `char_start` must be less than len and a char boundary
let ch = text.get(char_start..).unwrap().chars().next().unwrap();
let char_range = char_start..char_start + ch.len_utf8();
panic!(
"byte index {} is not a char boundary; it is inside {:?} (bytes {:?})",
offset, ch, char_range,
);
}
#[cold]
#[inline(never)]
#[track_caller]
fn log_err_char_boundary(text: &str, offset: usize) {
if offset >= text.len() {
log::error!(
"byte index {} is out of bounds of `{:?}` (length: {})",
offset,
text,
text.len()
);
return;
}View on GitHub (pinned to f4178619ac)
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
Example fix
// before: fixed byte step may land inside a multi-byte character
let cut = start + 20;
let left = rope.byte_slice(..cut);
// after: snap down to the enclosing char boundary
fn floor_char_boundary(bytes: &[u8], mut i: usize) -> usize {
i = i.min(bytes.len());
while i > 0 && (bytes[i] & 0xC0) == 0x80 {
i -= 1;
}
i
}
let cut = floor_char_boundary(&bytes, start + 20);
let left = rope.byte_slice(..cut); Defensive patterns
Strategy: validation
Validate before calling
fn floor_char_boundary(bytes: &[u8], mut i: usize) -> usize {
i = i.min(bytes.len());
while i > 0 && (bytes[i] & 0xC0) == 0x80 {
i -= 1;
}
i
}
// snap before slicing
let cut = floor_char_boundary(&bytes, desired_offset); Type guard
fn is_valid_boundary(text: &str, offset: usize) -> bool {
offset <= text.len() && text.is_char_boundary(offset)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- byte index {} is out of bounds of `{:?}` (length: {})
- Failed to read path: {path:?}
- byte index {} is out of bounds of rope (length: {})
- unsupported TOML value in .env.toml for key {}
- database not initialized
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/9be16d5458cfed5c.
Report an issue: GitHub.