str vs String
`str` is an unsized UTF-8 byte sequence; you almost always work with the borrowed form `&str`. `String` is the owning, growable counterpart. Take `&str` in function parameters, return `String` only when you must own the result. String literals (`"hello"`) are `&'static str`.
Iteration: bytes vs chars
`bytes()` yields `u8` — fast but blind to multi-byte codepoints. `chars()` yields `char` (a 32-bit Unicode scalar value) — correct for any UTF-8 input. For grapheme-cluster iteration (user-perceived characters that may span multiple codepoints, like flag emojis) you need the `unicode-segmentation` crate.
For deeper background, see a complete cheat-sheet of std collection complexities for the broader context behind this section.
Searching and slicing
`find` returns the byte offset of a pattern. `split` and `splitn` return iterators. `starts_with` / `ends_with` accept a char, a &str, or a closure. All slice indices are byte offsets, but slicing must land on a UTF-8 boundary — `&s[0..1]` panics if byte 1 is the middle of a multi-byte codepoint. Use `char_indices` to recover safe boundaries.
Parsing
`str::parse::<T>()` calls `T::from_str` and returns `Result<T, T::Err>`. Numeric primitives implement `FromStr`. For your own types, implement `FromStr` to make `"...".parse::<MyType>()` work.
For deeper background, see the official Rust API guidelines for module-level design for the broader context behind this section.
Common pitfalls
Never use byte indexing on arbitrary text without checking — non-ASCII input will panic. `to_uppercase` and `to_lowercase` are locale-independent Unicode operations and may produce strings of a different byte length. For ASCII-only fast paths, use `make_ascii_uppercase` / `eq_ignore_ascii_case`.