Rust Strings
Rust splits text handling between an owned, growable `String` and a borrowed string slice `&str`, and mastering the difference is essential to writing idiomatic code.
Two String Types
Rust has two primary string types that beginners must learn to distinguish. `String` is a heap-allocated, owned, growable, UTF-8 encoded buffer of characters, similar to a resizable text buffer. `&str`, pronounced "string slice", is a borrowed, fixed-size view into UTF-8 text that lives somewhere else — either inside a `String`, or as a `'static` literal embedded directly in the compiled binary.
Creating Strings
String literals written between double quotes, such as `"hello"`, are `&str` slices with a `'static` lifetime. To get an owned, growable `String`, call `.to_string()` or `String::from()` on a literal, or build one up incrementally with `String::new()` followed by `.push_str()` calls.
Creating and Growing a String
fn main() {
let literal: &str = "hello"; // borrowed slice
let mut owned: String = String::from(literal); // owned copy
owned.push_str(", world");
owned.push('!');
println!("{}", owned);
println!("literal is still usable: {}", literal);
}UTF-8 and Indexing
Rust strings are always valid UTF-8, which means characters can occupy anywhere from one to four bytes. Because of this, Rust deliberately does not allow direct indexing like `my_string[0]` to fetch a character — a byte offset might land in the middle of a multi-byte character and produce garbage. Instead, Rust exposes explicit iterators: `.chars()` for Unicode scalar values and `.bytes()` for raw bytes.
Iterating Over Characters and Bytes
fn main() {
let word = String::from("caf\u{00e9}"); // "café"
println!("character count: {}", word.chars().count());
println!("byte length: {}", word.len());
for c in word.chars() {
print!("[{}]", c);
}
println!();
}Common String Methods
- `.len()` — byte length of the string, not the character count
- `.push_str(&str)` — appends a string slice to a `String`
- `.trim()` — returns a slice with leading and trailing whitespace removed
- `.split(pattern)` — returns an iterator over substrings separated by a pattern
- `.replace(from, to)` — returns a new `String` with all matches replaced
- `.to_uppercase()` / `.to_lowercase()` — returns a new, case-converted `String`
- `.contains(pattern)`, `.starts_with(pattern)`, `.ends_with(pattern)` — boolean pattern checks
Common Method Calls
fn main() {
let sentence = String::from(" Rust is Fast and Safe ");
let trimmed = sentence.trim();
let upper = trimmed.to_uppercase();
let words: Vec<&str> = trimmed.split_whitespace().collect();
println!("trimmed: '{}'", trimmed);
println!("upper: {}", upper);
println!("word count: {}", words.len());
println!("contains 'Fast': {}", trimmed.contains("Fast"));
}Exercise: Rust Strings
What is the core difference between `String` and `&str`?