Build the mental model
A slice is a reference type that points to a contiguous portion of a collection (a string, an array) without taking ownership of it — an array slice `&[1, 2, 3]` gives you a "view" of the whole array or part of it and never owns any data itself. A string slice (`&str`) is a special case of this concept: a borrowed view into a `String`'s owned, growable, heap-allocated data — and string literals (`"hello"`) are also of type `&str`, embedded as compile-time constants directly in the binary. `String` is owned, mutable, and heap-allocated, growing at runtime and modifiable with things like `push_str` or `+`, while `&str` is a borrowed view and therefore immutable. Writing a function parameter's type as `&str` lets it accept both a `String` (via `&my_string`) and a `&str` literal, making it more flexible — a direct, practical example of Lesson 7's "borrow-first design." The key safety guarantee is that the borrow checker prevents the underlying data from being mutated while a slice into it is held, protecting against the classic C-style "dangling slice/pointer" bug entirely at compile time.
Connect it to a real scenario
Write the content analyzer's functions as `fn first_paragraph(text: &str) -> &str` — both the input parameter (`&str`) and return value (`&str`) work with either a `String` (via `&lesson_body`) or a string literal. To extract a lesson body's first 100 characters as preview text, use slice syntax `&text[..100]` — while that slice is held, the underlying `String` cannot be mutated, a direct hands-on example of the borrow checker at work. Store tags as `Vec<String>`, but write functions that filter or search tags with a `&[String]` (slice) parameter, so the caller never needs to move the whole `Vec`.
Try the working example
fn first_paragraph(text: &str) -> &str {
match text.find("\n\n") {
Some(pos) => &text[..pos],
None => text,
}
}
fn main() {
let owned = String::from("Ownership basics.\n\nRust prevents data races.");
println!("{}", first_paragraph(&owned));
println!("{}", first_paragraph("A literal works too.\n\nSecond part."));
}You print "Ownership basics." and "A literal works too.", proving the one function accepts both a String reference and a literal.5-minute try-it
Write `fn last_word(text: &str) -> &str` that returns the text's last word as a slice (split on whitespace) — test it with both a `String` variable and a string literal.
One important caution
Writing a function's parameter type as `&String` specifically — a string literal cannot be passed directly, giving up the flexibility that `&str` would provide.
Slicing a string by byte index (`&text[..5]`) without checking character boundaries — cutting through the middle of a multi-byte UTF-8 character (like accented or non-Latin text) causes a runtime panic.