str::chars¶
Level: reference · for working programmers
One line: An iterator over Unicode scalar values, yielding char — the closest thing Rust has to 'the characters', and still not what a reader would call one.
Stable since 1.0.0.
chars() decodes UTF-8 on the fly, so each item is a whole char however many bytes it took to store. That makes it the right iterator for almost everything: counting, filtering, mapping, reversing.
It is O(n) to count, and there is no s[i] shortcut, because finding character i means decoding the i before it. s.chars().nth(i) says that out loud.
A char is a Unicode scalar, not a grapheme. What a person points at as one character can be several: é may be one scalar or two (e + a combining accent), and a flag or a family emoji is several on purpose. So chars().count() is not "how many letters", and chars().rev() can reorder a combining mark away from the letter it belongs to. When you need what a reader sees, that is grapheme segmentation, and it lives outside std in the unicode-segmentation crate.
For the byte offsets you can slice at, use char_indices — chars().enumerate() gives ordinals, which are not offsets.
Example¶
str_chars.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.
fn main() {
let s = "héllo";
println!("{:?}", s.chars().collect::<Vec<char>>());
println!("{} chars from {} bytes", s.chars().count(), s.len());
// The whole iterator toolkit applies.
println!("{}", s.chars().filter(|c| c.is_alphabetic()).count());
println!("{}", s.chars().rev().collect::<String>());
println!("{}", s.chars().map(|c| c.to_ascii_uppercase()).collect::<String>());
// There is no s[i]; nth() decodes its way there, and says so by being O(n).
println!("{:?}", s.chars().nth(1));
// A scalar is not a grapheme: same visible text, two different counts.
let precomposed = "é"; // U+00E9
let combining = "e\u{301}"; // 'e' + COMBINING ACUTE ACCENT
println!("{} vs {} chars, printed {} and {}",
precomposed.chars().count(), combining.chars().count(),
precomposed, combining);
}
Verified output of str_chars.rs — regenerated by tools/run_examples.py, never hand-typed.
['h', 'é', 'l', 'l', 'o']
5 chars from 6 bytes
5
olléh
HéLLO
Some('é')
1 vs 2 chars, printed é and é
See also¶
str::char_indices— the same characters, with sliceable byte offsetsstr::bytes— the storage unit underneathstr::len— why the byte count and the char count differstr::split_whitespace— when the unit you want is a word, not a character
str::chars in the standard library ↗
Po polsku¶
Polskie słowo „znak” jest szersze niż char: char to skalar Unicode, a nie to, co czytelnik pokazuje palcem jako jedną literę. Zwykle się to nie rozjeżdża, bo w postaci znormalizowanej NFC każda polska litera — ą, ć, ę, ł, ń, ó, ś, ź, ż — jest jednym skalarem i "żółw".chars().count() daje 4. Ale ten sam tekst w NFD (tak bywa z tekstem skopiowanym z macOS-a albo wyjętym z bazy o innej normalizacji) rozkłada się na literę bazową plus znak łączący, więc ta sama funkcja odpowie 6, a chars().rev() oderwie wtedy diakrytyk od jego litery i doklei go do sąsiedniej. Domyka to jeden ładny szczegół: ł jako jedyna z tych liter nie ma rozkładu kanonicznego i w NFD zostaje sobą.
Kiedy naprawdę chodzi o „to, co widzi czytelnik”, potrzebna jest segmentacja na grafemy — w std jej nie ma, mieszka w crate'cie unicode-segmentation. I jeszcze jedno przyzwyczajenie z Pythona warte porzucenia: s[i] tutaj nie istnieje, bo żeby dojść do znaku i, trzeba zdekodować wszystkie wcześniejsze; s.chars().nth(i) mówi to wprost i jest O(n).
Szukaj po polsku: skalar Unicode a grafem · normalizacja NFC NFD · znaki łączące · rust str chars · rust unicode-segmentation graphemes