RustArena

Step 2 of 7

Slicing safely at char boundaries

You've sliced strings with &s[a..b]. The numbers are byte positions. That's fine as long as both ends fall on a char boundary (the start of a character); if one lands inside a multi-byte character, the program panics.

fn main() {
    let s = String::from("café");
    println!("slicing...");
    println!("{}", &s[0..4]);
}
slicing...

The panic message is end byte index 4 is not a char boundary; it is inside 'é' (bytes 3..5 of string). This is a real bug class at work: code that cuts names to "the first 10 characters" with &name[..10] passes every test written in English, then crashes on the first user named Zoë or 李.

Finding where characters start

char_indices() gives (byte position, char) pairs, so you can find safe places to cut:

fn main() {
    let s = "naïve café";
    for (i, c) in s.char_indices() {
        print!("{i}:{c} ");
    }
    println!();
    println!("{:?} {:?}", s.get(0..3), s.get(0..4));
    println!("{}", s.is_char_boundary(3));
}
0:n 1:a 2:ï 4:v 5:e 6:  7:c 8:a 9:f 10:é
None Some("naï")
false

Cutting after n characters

The nth entry of char_indices() tells you the byte where character number n starts, which is exactly where to cut to keep n characters:

fn preview(s: &str, max_chars: usize) -> &str {
    match s.char_indices().nth(max_chars) {
        Some((byte_pos, _)) => &s[..byte_pos],
        None => s,
    }
}

fn main() {
    println!("{}", preview("crème brûlée", 5));
    println!("{}", preview("🦀🦀🦀", 2));
    println!("{}", preview("ok", 10));
}
crème
🦀🦀
ok

&s[..byte_pos] means "from the start up to byte_pos" (and &s[i..] means "from i to the end"). If there's no character number max_chars, the string is short enough and is returned whole.

Common mistakes

Your task

Write fn preview(text: &str, max_chars: usize) -> String for a chat app's notification preview. If text has more than max_chars characters, return the first max_chars characters followed by ...; otherwise return the whole text.

Challenge 2

Write fn split_at_char(s: &str, n: usize) -> (&str, &str) that splits after the first n characters (not bytes). If the string has n characters or fewer, return (s, ""). Use char_indices to find the byte position, then the slice method split_at(byte) from module 11.

Challenge 3

A support tool shows masked usernames. For each input line (one username), print it with the first two characters kept and every following character replaced by *: ferris becomes fe**** and zoë becomes zo*. Names with two characters or fewer are printed unchanged.

Previous: UTF-8: bytes, chars and why s[0] fails Next: Splitting: lines, split, splitn, split_once