C.W.K.
Stream
Lesson 02 of 06 · published

String vs &str

~11 min · collections, string, str, utf8

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

Text in Rust is split across two types, and the split confuses everyone at first: String and &str. The distinction is exactly the owned-vs-borrowed split you already know.

String owns; &str borrows

String is a growable, heap-allocated, owned UTF-8 buffer — you can append to it, and it frees its memory when dropped. &str (a 'string slice') is a borrowed view into UTF-8 text someone else owns — a window, not an owner. A string literal "hi" is a &str pointing into your binary.

Which to use where

Take &str in function parameters — it accepts both literals and borrowed Strings. Return or store String when you need ownership. Build a String with String::from, .to_string(), or format!; append with push_str. The &String to &str coercion happens automatically, so &str parameters 'just work' with both.

UTF-8 means no integer indexing. You can't write s[0] on a String — a character may be multiple bytes, so indexing by byte could split one. Iterate with .chars() for characters or .bytes() for raw bytes, and slice only on known char boundaries. Rust makes you respect Unicode.

Why two types is actually simpler

It feels like overhead until you internalize it: the type tells you who owns the text. String in a struct = the struct owns its text and has no lifetime concerns. &str = a borrow tied to a source. Once owned-vs-borrowed clicks (it did in the Borrowing track), the two string types stop being confusing and start being informative.

Code

Owned String, borrowed &str, and char iteration·rust
fn shout(text: &str) -> String {       // take &str, return owned String
    format!("{}!", text.to_uppercase())
}

fn main() {
    let owned = String::from("hello");  // owns a heap buffer
    let literal = "world";              // &'static str baked into the binary

    println!("{}", shout(&owned));      // &String coerces to &str
    println!("{}", shout(literal));     // literal is already &str

    // UTF-8: iterate chars, don't byte-index
    for c in "héllo".chars() {
        print!("{c} ");
    }
    println!();
}

External links

Exercise

Write fn initials(name: &str) -> String that returns the first letter of each space-separated word, uppercased. Call it with a literal and a String. Then try name[0] and read why Rust won't let you index a string by integer.
Hint
Use .split_whitespace() and .chars().next(). Integer indexing is forbidden because a UTF-8 char can span multiple bytes — name[0] could land mid-character, so Rust makes you iterate chars instead.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.