UTF-8 & Indexing
Rust str is UTF-8. Indexing s[i] is byte-based and panics on invalid char boundaries.
Recipe
fn safe_prefix(s: &str, max_bytes: usize) -> &str {
if s.len() <= max_bytes { return s; }
let mut end = max_bytes;
while !s.is_char_boundary(end) { end -= 1; }
&s[..end]
}When to reach for this: Truncating UI strings, parsing protocols, any substring by length.
Working Example
fn main() {
let s = "Hello 🦀";
// let bad = &s[0..7]; // may panic
for (i, ch) in s.char_indices() {
println!("{i}: {ch}");
}
}What this demonstrates:
char_indicesgives byte index at char boundary- Emoji uses 4 bytes in UTF-8
- Safe slicing only at
is_char_boundary
Deep Dive
len() returns bytes. Use chars(), bytes(), or graphemes (unicode-segmentation crate) for user-perceived characters.
Gotchas
- s[n] char access - Not available. Fix:
s.chars().nth(n). - Assume 1 byte per char - Wrong for UTF-8. Fix: Iterate chars.
- Truncation mid-codepoint - Invalid str if forced. Fix:
is_char_boundary. - OsString not UTF-8 - Platform bytes. Fix: Separate type.
- Byte index from user - Dangerous. Fix: Char/grapheme index API.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
chars().nth | Char by index | Need O(1) random char |
unicode-segmentation | User graphemes | ASCII-only internal |
Vec<char> | Repeated char index | Memory overhead ok |
FAQs
why no s[i]?
O(1) char index impossible UTF-8.is_char_boundary?
Check before slice at byte index.chars nth O(n)?
Yes linear scan.encode utf16?
encode_utf16 iterator.from_utf8?
Validate bytes to &str Result.lossy?
String::from_utf8_lossy replacement char.str ref lifetime?
Borrowed from String or static.ascii fast path?
is_ascii check optimize.serde str?
UTF-8 JSON strings.clippy?
indexing slices warns on str sometimes.Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.