Skip to content

str::char_indices

str methods · Strings

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.

pub fn char_indices(&self) -> CharIndices<'_>

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::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