Strings and Text Processing

chars()

The .chars() method on str returns a std::str::Chars<'_> iterator that decodes the string's UTF-8 bytes on the fly and yields each Unicode scalar value (char) by value.

Signature and Chars<'_> Capabilities

pub fn chars(&self) -> std::str::Chars<'_>
// where Chars<'_>: Iterator<Item = char> + DoubleEndedIterator + FusedIterator

Because UTF-8 is variable-width (1..=4 bytes per char):

  • Chars<'_> implements DoubleEndedIterator (allowing .rev() and .next_back() because UTF-8 continuation bytes 10xxxxxx can be scanned backward unambiguously).
  • Chars<'_> does not implement ExactSizeIterator because knowing the exact number of chars requires decoding the UTF-8 stream ($O(n)$).
UTF-8 Bytes: [ 41,   C3, A9,   F0, 9F, A6, 80 ]
               │     └──┬──┘   └─────┬──────┘   .chars()
               ▼        ▼            ▼
Yields:       'A'      'é'          '🦀'

Iterating Forward and Backward Over Characters

fn main() {
    let word = "Aé🦀";

    print!("Forward:  ");
    for ch in word.chars() {
        print!("'{ch}' ");
    }
    println!();

    print!("Backward: ");
    for ch in word.chars().rev() {
        print!("'{ch}' ");
    }
    println!();
}
Forward:  'A' 'é' '🦀' 
Backward: '🦀' 'é' 'A' 

Accessing the First, Last, or $n$-th Character

Instead of indexing s[i], use .chars().next(), .chars().next_back(), or .chars().nth(n):

Operation Idiomatic Expression Time Complexity
First char s.chars().next() $O(1)$ (decodes 1–4 leading bytes)
Last char s.chars().next_back() (prefer over .last()) $O(1)$ (decodes 1–4 trailing bytes)
$n$-th char (0-indexed) s.chars().nth(n) $O(n)$ (advances through $n$ characters)

Note that calling s.chars().nth(i) inside a for i in 0..n loop is an $O(n^2)$ anti-pattern because each call starts scanning from byte 0 again. Always iterate with for ch in s.chars() or collect into a Vec<char> if repeated random access by character index is required.

Viewing the Remaining Subslice with Chars::as_str()

At any point while iterating with a Chars<'_> iterator, calling chars.as_str() returns the unconsumed remainder of the string as a &str in $O(1)$ time:

fn main() {
    let mut chars = "key=value".chars();
    assert_eq!(chars.next(), Some('k'));
    assert_eq!(chars.next(), Some('e'));
    assert_eq!(chars.next(), Some('y'));
    assert_eq!(chars.next(), Some('='));

    // View the remaining unconsumed slice directly as a `&str`:
    println!("Remaining slice: '{}'", chars.as_str());
}
Remaining slice: 'value'