str::char_indices¶
Level: reference · for working programmers
One line: Characters paired with the byte offset each one starts at — the iterator to use when the answer is going to be a slice endpoint.
Stable since 1.0.0.
Each item is (usize, char), where the usize is a byte offset into the string, guaranteed to be a character boundary. That guarantee is the entire reason the method exists: an offset from here can be used to slice, and cannot panic.
char_indices() is not chars().enumerate(). They agree until the first multi-byte character and never again:
"héllo" |
|
|---|---|
char_indices() |
(0,'h') (1,'é') (3,'l') (4,'l') (5,'o') |
chars().enumerate() |
(0,'h') (1,'é') (2,'l') (3,'l') (4,'o') |
enumerate counts characters; char_indices measures bytes. Slicing with an ordinal is a bug that works perfectly on ASCII and then panics — or silently cuts in the wrong place — on the first accented name.
The offset points at the start of the character. Its end is i + c.len_utf8(), which is how you slice a single character out.
Example¶
str_char_indices.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.
fn main() {
let s = "héllo";
for (i, c) in s.char_indices() {
let n = c.len_utf8();
println!("byte {i:>2} {c:?} ({n} byte{})", if n == 1 { "" } else { "s" });
}
// The two disagree the moment a character is wider than one byte.
let idx: Vec<usize> = s.char_indices().map(|(i, _)| i).collect();
let ord: Vec<usize> = s.chars().enumerate().map(|(i, _)| i).collect();
println!("offsets {idx:?}");
println!("ordinals {ord:?}");
// An offset from char_indices is always a legal slice endpoint.
let third = s.char_indices().nth(2).unwrap().0;
println!("from the third char: {:?}", &s[third..]);
// Slicing one character out: start, plus its own width.
let (i, c) = s.char_indices().nth(1).unwrap();
println!("just that char: {:?}", &s[i..i + c.len_utf8()]);
}
Verified output of str_char_indices.rs — regenerated by tools/run_examples.py, never hand-typed.
byte 0 'h' (1 byte)
byte 1 'é' (2 bytes)
byte 3 'l' (1 byte)
byte 4 'l' (1 byte)
byte 5 'o' (1 byte)
offsets [0, 1, 3, 4, 5]
ordinals [0, 1, 2, 3, 4]
from the third char: "llo"
just that char: "é"
See also¶
str::chars— the same characters without the offsetsstr::is_char_boundary— checking an offset you got from somewhere elsestr::find— when you want the offset of a pattern rather than of every characterstr::substr_range— the offsets of a slice you already hold
str::char_indices in the standard library ↗
Po polsku¶
Różnica między char_indices() a chars().enumerate() to dokładnie ten błąd, który w polskich danych wychodzi natychmiast, a w angielskich nie wyjdzie nigdy: enumerate numeruje znaki po kolei, char_indices podaje przesunięcie w bajtach, a te dwie liczby rozjeżdżają się przy pierwszej literze z diakrytykiem. W "Łukasz" trzecia litera ma numer porządkowy 2, ale offset 3 — i wycinek wzięty po numerze albo panikuje komunikatem byte index ... is not a char boundary, albo po cichu tnie w złym miejscu. Stąd reguła: jeżeli liczba ma trafić do &s[..], bierzemy ją z char_indices(), bo tylko ona ma gwarancję trafienia w granicę znaku. Offset wskazuje początek znaku, a jego koniec to i + c.len_utf8() — i właśnie tak wycina się z łańcucha pojedynczą literę.
Szukaj po polsku: przesunięcie w bajtach · granica znaku · polskie znaki a indeksowanie łańcucha · rust char_indices vs chars enumerate · rust byte index is not a char boundary