RustArena

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.

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

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)

Practice it