Strings and Text Processing
char_indices()
The .char_indices() method on str returns a std::str::CharIndices<'_> iterator that yields (usize, char) pairs, where the usize is the exact starting UTF-8 byte offset of that char within the string slice.
Why .char_indices() Differs from .chars().enumerate()
When a string contains multi-byte UTF-8 characters, the ordinal character index (0, 1, 2, ... from .chars().enumerate()) does not match the byte offset required for slicing (&s[byte_start..byte_end]). By contrast, .char_indices() always yields valid UTF-8 byte boundary offsets:
String: "Aé🦀B" (8 bytes total)
Byte index: 0 1 2 3 4 5 6 7
Bytes: [ 41, C3, A9, F0, 9F, A6, 80, 42 ]
Chars: 'A' 'é' '🦀' 'B'
.chars().enumerate() ──> (0, 'A'), (1, 'é'), (2, '🦀'), (3, 'B') (char ordinal)
.char_indices() ──> (0, 'A'), (1, 'é'), (3, '🦀'), (7, 'B') (UTF-8 byte offset)
fn main() {
let text = "Aé🦀B";
println!("char_indices() vs chars().enumerate():");
for ((char_ord, _), (byte_idx, ch)) in text.chars().enumerate().zip(text.char_indices()) {
println!(
" char #{char_ord}: '{ch}' starts at byte offset {byte_idx} (len_utf8 = {})",
ch.len_utf8()
);
}
}
char_indices() vs chars().enumerate():
char #0: 'A' starts at byte offset 0 (len_utf8 = 1)
char #1: 'é' starts at byte offset 1 (len_utf8 = 2)
char #2: '🦀' starts at byte offset 3 (len_utf8 = 4)
char #3: 'B' starts at byte offset 7 (len_utf8 = 1)
Safe Substring Slicing Using .char_indices()
Because every byte_idx returned by .char_indices() is guaranteed to lie on a valid UTF-8 character boundary (and byte_idx + ch.len_utf8() is the exact end boundary of that character), you can safely slice the original &str without risking a runtime panic:
/// Returns a `&str` containing at most the first `max_chars` Unicode characters.
fn truncate_chars(s: &str, max_chars: usize) -> &str {
match s.char_indices().nth(max_chars) {
Some((byte_idx, _ch)) => &s[..byte_idx],
None => s,
}
}
fn main() {
let greeting = "স্বাগতম Rust";
println!("First 4 chars: '{}'", truncate_chars(greeting, 4));
println!("First 20 chars: '{}'", truncate_chars(greeting, 20));
}
First 4 chars: 'স্বা'
First 20 chars: 'স্বাগতম Rust'