Why can't you index into a String?
Short answer: a String is a buffer of UTF-8 bytes, and a character is one to four of them, so s[0] can’t say whether 0 means a byte or a character — the compiler refuses instead of guessing. Vec indexing works because every element is the same size; a string has no fixed stride to multiply by. Write s.chars().next() for the first character, s.as_bytes()[0] for the first byte.
From a fresh project — cargo new index-demo, replace src/main.rs with the block below, then cargo build.
fn main() { let s = String::from("café"); let c = s[0]; println!("{c}");}error[E0277]: the type `str` cannot be indexed by `{integer}` --> src/main.rs:3:15 |3 | let c = s[0]; | ^ string indices are ranges of `usize` | = help: the trait `SliceIndex<str>` is not implemented for `{integer}` = note: you can use `.chars().nth()` or `.bytes().nth()` for more information, see chapter 8 in The Book: <https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings>help: `usize` implements trait `SliceIndex<T>` --> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/slice/index.rs:179:0 | = note: `SliceIndex<[T]>` --> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/bstr/traits.rs:197:0 | = note: `SliceIndex<ByteStr>` = note: required for `String` to implement `Index<{integer}>`
For more information about this error, try `rustc --explain E0277`.The headline is the rule, and it says str even for a String: indexing is looked up through the Deref to str, and the closing note, “required for String to implement Index<{integer}>”, spells out that chain. The label under the code is the rule itself: string indices are ranges of usize. Number indexing doesn’t exist; range indexing does — the slicing the previous article covers. A &str gives the same error.
Why a number can’t work
Section titled “Why a number can’t work”“café” is five bytes and four characters: s.len() is 5, s.chars().count() is 4. The é at the end is two bytes, 195 and 169, so the fourth character starts at byte 3. Ask for s[3] and there are two defensible answers: byte 3, half of the é, or character 3, the é itself. No arithmetic turns one into the other: finding the n-th character means walking from the start, reading each character’s width before the next one’s position is known. The compiler’s note points at .chars().nth() and .bytes().nth() — methods that walk.
What to write instead
Section titled “What to write instead”s.chars().nth(n)— the n-th character, anOption<char>:Some('é')for the fourth character of “café”. Each call walks from the start; a loop overchars()beatsnthper index.s.chars().next()— the first character, without the walk.s.as_bytes()[n]— the n-th byte, au8: 99 for “café”. Only meaningful when the text is known to be ASCII; elsewhere a byte can be half a character.&s[a..b]— a range of bytes, the indexing that does exist, under the char-boundary rules from the slicing article.
Related
Section titled “Related”- What is the difference between String and &str? — the buffer underneath
- How to iterate over the characters of a string —
chars()andbytes() - How to slice a string safely — the range indexing that works
- Working with text — the rest of the chapter