Rust Slices and String vs &str Explained
November 10, 2026
Welcome to episode 9 of Rust for Beginners, a series for complete beginners (no programming experience needed). In the last video, we learned about references. Today, we'll learn about a special kind of reference, the slice, and finally answer: what's the difference between String and ampersand str? Every code block below is the exact code from the video, ready to copy.
- All episodes are in the Rust Tutorial for Beginners playlist.
- Versions used: Rust 1.99.1 on Ubuntu, VS Code with rust-analyzer.
1. New project
Let's create a new project called slices, and open it in VS Code.
cargo new slices
cd slices2. Slice
A slice is a reference to part of something. Ampersand text, zero dot dot five, borrows the first five bytes: hello.
src/main.rs:
fn main() {
let text = String::from("hello world");
let hello = &text[0..5];
let world = &text[6..11];
println!("{hello} | {world}");
println!("{} | {}", &text[..5], &text[6..]);
}Output: hello | world / hello | world
3. Range
The start is included, the end is not. Six dot dot eleven gives us world.
4. Short forms
We can leave out the start to begin at zero, or the end to go to the very end.
5. Run A
Let's run it.
cargo run6. first_word
Slices are great as return values. first word goes through the text, and when it finds a space, it returns the slice before it. char indices gives each character together with its position.
fn main() {
let text = String::from("hello world");
let word = first_word(&text);
println!("First word: {word}");
}
fn first_word(s: &str) -> &str {
for (i, c) in s.char_indices() {
if c == ' ' {
return &s[..i];
}
}
s
}Output: First word: hello
Stage B2 โ let mut text, and text.clear(); after the call
fn main() {
let mut text = String::from("hello world");
let word = first_word(&text);
text.clear();
println!("First word: {word}");
}Error:
error[E0502]: cannot borrow `text` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let word = first_word(&text);
| ----- immutable borrow occurs here
4 | text.clear();
| ^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("First word: {word}");
| ---- immutable borrow later used here7. Safety
Let's run it. Now, what if we clear the text while we still hold the word?
cargo run8. Error
Rust says no. The word borrows the text, so we can't change the text while the word is still in use. Without this check, word would point at deleted data.
9. String vs &str
Now, the big question. A String owns its text, lives on the heap, and can grow. An ampersand str is a string slice: it only borrows text that lives somewhere else.
fn main() {
let literal: &str = "I am a string literal";
let mut owned: String = String::from("I can grow");
owned.push_str("!");
greet(literal);
greet(&owned);
greet("Ferris");
let copy: String = literal.to_string();
println!("{copy}");
}
fn greet(text: &str) {
println!("Got: {text}");
}Output: Got: I am a string literal / Got: I can grow! / Got: Ferris / I am a string literal
10. Literals
A string literal in quotes is an ampersand str. Its text is stored inside the program itself.
11. Parameters
For function parameters, prefer ampersand str. greet works with a literal, with a borrowed String, and with any text.
12. to_string
To turn an ampersand str into an owned String, call to string.
13. Run C
Let's run it.
cargo run14. Array slices
Slices work on arrays too. This takes the middle three numbers. To print it, we use colon question mark, the debug format.
fn main() {
let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4];
println!("{middle:?}");
println!("Length: {}", middle.len());
}Output: [20, 30, 40] / Length: 3
15. Run D
Twenty, thirty, forty, and the length is three.
cargo runRecap
To recap: a slice borrows part of a string or an array. String owns its text and can grow. Ampersand str borrows text. And for parameters, ampersand str works with both.