String vs &str in Rust: what's the difference?
Rust's two string types explained: owned, growable String vs borrowed &str slices, when to use each, and how to convert between them.
Rust has two main string types, and every beginner asks why.
Stringis an owned, growable piece of text. It keeps its bytes on the heap, can be changed (push_str,push), and frees its memory when it goes out of scope.&str(pronounced "string slice") is a borrowed view of text that lives somewhere else: inside aString, or baked into your program's binary. It's just a pointer and a length, and you can't grow it.
A string literal like "hello" is a &str (precisely &'static str, because the text lives as long as the program).
let owned = String::from("Ferris"); let view: &str = &owned[0..3];
owned (stack) heap view (stack)
┌─────────────┐ ┌─┬─┬─┬─┬─┬─┐ ┌─────────────┐
│ ptr ────────┼────────► │F│e│r│r│i│s│ ◄───┼──── ptr │
│ len 6 │ └─┴─┴─┴─┴─┴─┘ │ len 3 │
│ capacity 6 │ └─────────────┘
└─────────────┘
The relationship is the same as Vec<T> and &[T]: one owns the data, the other borrows part of it.
Which one to use
| Situation | Use | Why |
|---|---|---|
| Function parameter you only read | &str |
accepts literals, &String and slices |
| Struct field | String |
the struct should own its data |
| Building or changing text | String |
only String can grow |
| Returning newly created text | String |
a &str would point at a local that's about to be freed |
| Fixed text in your code | &'static str |
literals cost nothing |
Taking &str parameters is the single most useful habit: fn greet(name: &str) works with greet("Ferris"), greet(&my_string) and greet(&line[..5]). A &String turns into a &str automatically (this is deref coercion). Clippy flags &String parameters (ptr_arg) for this reason.
Converting
&strtoString:String::from("hi"),"hi".to_string(),"hi".to_owned(), orformat!(...). All allocate a copy.Stringto&str:&s,s.as_str(), or a slice&s[1..4]. Free: no copy.
Common mistakes
Giving a literal where a String is expected:
fn main() {
let name: String = "Ferris";
println!("{name}");
}
error[E0308]: mismatched types
--> src/main.rs:2:24
|
2 | let name: String = "Ferris";
| ------ ^^^^^^^^ expected `String`, found `&str`
| |
| expected due to this
|
help: try using a conversion method
|
2 | let name: String = "Ferris".to_string();
| ++++++++++++
Adding two &str with +:
fn main() {
let a = "Hello, ";
let b = "world";
let c = a + b;
println!("{c}");
}
error[E0369]: cannot add `&str` to `&str`
+ needs an owned String on the left (it reuses that buffer): a.to_string() + b works. Usually format!("{a}{b}") is clearer.
Also watch out for returning a &str that points into a local String: the String is freed when the function returns, so the compiler refuses (E0515 or E0106). Return the String itself.
Example
fn greet(name: &str) -> String {
format!("Welcome to the arena, {name}!")
}
struct Player {
name: String,
}
fn main() {
let literal: &str = "Ferris";
let owned: String = String::from("Corro the Unsafe");
let player = Player { name: literal.to_string() };
println!("{}", greet(literal));
println!("{}", greet(&owned));
println!("{}", greet(&player.name));
let first_word: &str = &owned[0..5];
println!("slice: {first_word}");
let mut log = String::new();
log.push_str("round 1");
log.push(';');
log += " round 2";
println!("{log} ({} bytes)", log.len());
}
Output:
Welcome to the arena, Ferris!
Welcome to the arena, Corro the Unsafe!
Welcome to the arena, Ferris!
slice: Corro
round 1; round 2 (16 bytes)