Rust Strings & Text
Rust has two string flavors:
&str — a borrowed, fixed-length slice of text — and String — an owned, growable buffer on the heap. Understanding which one to reach for is the first step toward comfortable text handling.
&str versus String
A string literal like "hello" has type &str: it borrows text that already exists. A String owns its growable heap buffer. The table contrasts the two:
| &str | String |
|---|---|
| Borrowed view (a "string slice") | Owned, heap-allocated buffer |
| Fixed length, immutable | Growable, mutable |
| Cheap to copy (pointer + length) | Clone allocates new memory |
| From literals or slicing a String | From String::from, to_string, format! |
fn main() {
let literal: &str = "hello"; // &str: a view over static text
let owned: String = String::from("hi"); // String: owns a growable buffer
let from_literal = "hi".to_string(); // same, via the ToString trait
// a String hands out &str slices cheaply (deref coercion)
let slice: &str = &owned;
println!("{} / {} / {} / {}", literal, owned, from_literal, slice);
}
Building Strings
String grows with push (one character) and push_str (a whole slice). For complex formatting, format! builds a new String, and + concatenates from left to right:
fn main() {
let mut s = String::from("Rust");
s.push('!'); // append a single char
s.push_str(" is fast"); // append a whole &str
println!("{s}"); // inline-capture format (Rust 1.58+)
// format! returns an owned String built from a template
let page = format!("Read {title} by {author}", title = "the Book", author = "Klabnik");
println!("{page}");
// '+' takes ownership of the left operand and appends the right
let greeting = String::from("Hello, ") + "world!";
println!("{greeting}");
}
Common Methods
Most text work is a chain of small methods. Note that len() counts bytes, not characters — crucial for non-ASCII text:
fn main() {
let hello = String::from("Hello, world");
println!("len (bytes) = {}", hello.len()); // 12
println!("contains = {}", hello.contains("world"));
println!("replaced = {}", hello.replace("world", "Rust"));
println!("uppercase = {}", hello.to_uppercase());
for word in hello.split(", ") {
println!("part: {word}");
}
let trimmed = " padded ".trim();
println!("trimmed = '{}'", trimmed);
}
UTF-8 and Indexing
Rust strings are valid UTF-8. Because one "character" can span several bytes, indexing by position (s[0]) is a compile error — use chars() for Unicode scalar values or bytes() for raw bytes:
fn main() {
let s = String::from("Rúst"); // 'ú' is 2 bytes in UTF-8
println!("bytes = {}", s.len()); // 5, not 4
for ch in s.chars() { // iterates 4 chars: R ú s t
print!("{ch} ");
}
println!();
for b in s.bytes() { // iterates 5 raw bytes
print!("{b} ");
}
println!();
}
Practice move: open the lab examples and modify the strings demo — append, format, then iterate
chars() vs bytes() on a word like "naïve" and predict each length before running.