Chars, Bytes & Graphemes
Iterate str as Unicode scalars (chars), raw bytes (bytes), or user-perceived grapheme clusters (crate).
Recipe
fn main() {
let s = "e\u{0301}"; // e + combining acute
println!("chars: {}", s.chars().count()); // may be 2
}When to reach for this: Correct length limits, cursor movement, collation - not raw bytes.
Working Example
fn ascii_words(s: &str) -> impl Iterator<Item = &str> {
s.split_whitespace()
}
fn byte_len(s: &str) -> usize {
s.as_bytes().len()
}
fn main() {
let text = "Hello 世界";
println!("bytes={} chars={}", byte_len(text), text.chars().count());
for w in ascii_words(text) { println!("{w}"); }
}What this demonstrates:
chars().count()vslen()bytesas_bytes()for protocol work- Grapheme complexity needs
unicode-segmentationfor UI
Deep Dive
unicode-segmentation = "1"use unicode_segmentation::UnicodeSegmentation;
for g in "👨👩👧".graphemes(true) { println!("{g}"); }Gotchas
- chars == user characters - False for combining marks. Fix: Grapheme segmentation.
- bytes() for UTF-8 validation - Already valid in
&str. Fix:from_utf8on[u8]. - encode_utf16 for Windows - OS APIs sometimes UTF-16.
- char is 32-bit - Not UTF-8 encoded in char type.
- Performance chars nth - O(n).
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
unicode-segmentation | UI cursor | ASCII-only logs |
| bytes only | Binary protocol | Human text |
encode_utf16 | Windows FFI | Unix UTF-8 |
FAQs
chars double count emoji?
Some emoji multi scalar - graphemes fix.is_ascii?
Fast path all ASCII.from_utf8_unchecked?
Unsafe - must prove valid.encode_utf8 buffer?
encode_utf8 to stack array.width unicode?
unicode-width crate terminals.nfc nfd?
unicode-normalization crate.str pattern?
contains starts_with char-aware.no_std str?
core str same.serde char?
Single unicode scalar JSON string.test grapheme?
Assert segmentation count.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+.