String & &str
Rust has two string types: String (owned, heap, growable) and &str (borrowed slice). Both are UTF-8. Indexing by byte position can split a codepoint — the API uses methods instead.
String vs &str, slicing, iteration
EXAMPLE
fn main() {
// 1) Create
let owned: String = String::from("hello");
let borrowed: &str = "hello"; // string literal — &'static str
let from_lit: String = "hi".to_string();
let pushed = format!("{} world", owned);
// 2) Convert between
let s: String = borrowed.to_owned();
let view: &str = &s;
// 3) Grow
let mut name = String::from("Ada");
name.push(' ');
name.push_str("Lovelace");
name += "!";
// 4) UTF-8 reality — slicing by byte CAN split a codepoint
let café = String::from("café");
println!("{}", café.len()); // 5 — BYTES, not chars
// let bad = &café[0..3]; // OK — 'caf'
// let bad = &café[0..4]; // PANIC — splits 'é'
// Iterate properly
for c in café.chars() { print!("{c} "); }
for (i, c) in café.char_indices() { println!("{i} {c}"); }
for b in café.bytes() { print!("{b} "); }
// 5) Find + slice safely
if let Some(idx) = café.find('é') {
let (before, rest) = café.split_at(idx);
println!("{before} | {rest}");
}
// 6) String operations
let s = " Hello, World ";
s.trim();
s.to_lowercase();
s.replace(',', " -");
s.split(',').collect::<Vec<_>>();
s.contains("World");
s.starts_with(" Hello");
// 7) Parse + format
let n: i32 = "42".parse().expect("not a number");
let f: f64 = "3.14".parse().unwrap();
let msg = format!("got n={n} f={f:.2}");
}
Why it matters
&str is what you accept as a parameter; String is what you return when you own the data. Forcing callers to allocate just because your signature says String is the most common Rust API smell.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
let owned: String = String::from("hi");
let borrowed: &str = &owned;
println!("{} ({} chars)", borrowed, owned.chars().count());
Try it Yourself »
Discussion
Loading…