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

  1. 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
  2. Derive offsets from char/grapheme-aware APIs (char_indices, to_offset from a Point/Anchor) instead of raw byte arithmetic
  3. For LSP UTF-16 positions, use a dedicated utf16-to-byte-offset converter, never direct byte math
  4. 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

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


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/9be16d5458cfed5c. Report an issue: GitHub.