---
title: "5.4 Introduction to &str"
description: "String literals unmasked: the borrowed string type, why trim never copies, and how to choose function parameter types."
url: "https://learnrust.net/chapter-5/introduction-to-str/"
last_updated: "2026-06-12"
---

# 5.4 Introduction to &str

Time for the reveal this chapter has been circling. Every string literal you've written since your first program (that's `"Hello, world!"` itself) was never a `String`. Ask the compiler, via a deliberately wrong annotation, and it'll name the real type:

```rust
fn main() {
    let greeting: i32 = "Hello, world!";
    println!("{greeting}");
}
```

```
error[E0308]: mismatched types
 --> src/main.rs:2:25
  |
2 |     let greeting: i32 = "Hello, world!";
  |                   ---   ^^^^^^^^^^^^^^^ expected `i32`, found `&str`
  |                   |
  |                   expected due to this
```

There it is, the type that's been photobombing error messages since lesson [4.8](https://learnrust.net/chapter-4/char-and-unicode/): **&str** (pronounce it "string slice"; out loud, Rust folks often just say "stir"). A `&str` is a *view* of text that lives somewhere else. It doesn't own the text, can't grow it, can't change it; it just looks at it, and knows where it starts and how long it is.

## Owners and viewers

The cleanest way to hold both string types in your head at once is an analogy learncpp uses for the C++ equivalents, and it ports perfectly. Owning a bicycle is a commitment: you paid for it, you store it, you maintain it, and when you're done with it, disposing of it is your problem. *Watching* a bicycle through your window costs nothing: no purchase, no shed, no responsibility, and any number of neighbors can watch the same bicycle at once. But the watching only makes sense while the bicycle is actually there.

`String` is the owner: it has its own copy of the text and the storage that holds it (last lesson's "this one's mine"). `&str` is the viewer at the window: a cheap way to *refer to* text that something else keeps alive. A string literal's text is baked directly into your compiled program, so a literal like `"hello"` is a view of text that's there for the program's entire run, which is why a `&str` variable can use it forever without anyone owning anything.

## You've been using views all chapter

Now the trim mystery from lessons [1.6](https://learnrust.net/chapter-1/reading-input/) and [5.2](https://learnrust.net/chapter-5/shadowing/) dissolves. `trim` takes your `String` and returns... what, exactly? It doesn't build a cleaned-up copy of the text. It returns a `&str`: a view into the *same* text the `String` owns, with the start nudged past the leading whitespace and the length shortened to skip the trailing newline. Nothing is copied. Nothing is allocated. The idiom

```rust
let name = name.trim();
```

is a `String` being shadowed by a view into itself, which is why it's so cheap, and why 5.2 could only wink at the type. (It's also your first hint of a deep Rust habit: when an operation *can* be done by viewing instead of copying, Rust's standard library almost always does, and tells you so in the return type.)

## &str as the parameter type

The practical payoff of views is function signatures. Suppose you're writing a greeter:

```rust
fn greet(name: &str) {
    println!("Welcome, {name}!");
}

fn main() {
    greet("Ada");

    let typed = String::from("Grace");
    greet(&typed);
}
```

```
Welcome, Ada!
Welcome, Grace!
```

One function, both flavors welcome. A literal is already a `&str`, so it walks right in. For the `String`, writing `&typed` lends the function a view of the owned text (that `&` is the same "lend, don't hand over" marker you've been writing in `read_line(&mut name)` since lesson 1.6, minus the permission to modify; chapter 9 is entirely about this machinery, and lesson 9.2 settles the tab). Forget the `&` and the compiler draws the type boundary for you:

```rust
fn greet(name: &str) {
    println!("Welcome, {name}!");
}

fn main() {
    let typed = String::from("Grace");
    greet(typed);
}
```

```
error[E0308]: mismatched types
 --> src/main.rs:7:11
  |
7 |     greet(typed);
  |     ----- ^^^^^ expected `&str`, found `String`
  |     |
  |     arguments to this function are incorrect
  |
note: function defined here
 --> src/main.rs:1:4
  |
1 | fn greet(name: &str) {
  |    ^^^^^ ----------
help: consider borrowing here
  |
7 |     greet(&typed);
  |           +
```

The function wants a view; you offered the whole bicycle; the compiler suggests the window.

> **Best practice**
>
> Store text in a `String`; accept text parameters as `&str`. An `&str` parameter takes literals and (with `&`) Strings alike, costs nothing to pass, and promises the caller you'll only look.

> **Key insight**
>
> If C++ is your previous language, the viewer pattern should be raising an alarm: what happens when a view outlives the text it watches? `std::string_view` makes that a famous family of bugs (the bicycle is gone; the neighbor is still staring at the empty driveway). In Rust, the question has a different answer: *programs where that can happen do not compile.* How the compiler proves it can't happen is the borrow checker, the star of chapter 9 and the single most distinctive thing about Rust. Consider this chapter the calm before that storm.

What this lesson deliberately skips: making a view of *part* of a string (the first word, say). Carving views by range is real and routine, but it rides on machinery from chapters 9 and 18, so it waits. Two string types, one relationship (owner, viewer) is the entire syllabus for today.

## Quiz time

**Question #1**

Name the type of each variable, no compiler allowed:

```rust
fn main() {
    let a = "library";
    let b = String::from("library");
    let c = b.trim();
    let d = c.len();
    println!("{a} {b} {c} {d}");
}
```

<details class="solution">
<summary>Show solution</summary>

`a` is a `&str` (literals always are). `b` is a `String` (it says so). `c` is a `&str`: `trim` returns a view into `b`'s text (here an unmoved one, since there's no whitespace to skip). `d` is a `usize`, lesson [4.3](https://learnrust.net/chapter-4/isize-usize-and-integer-literals/)'s memory-measurement type, counting 7 bytes.

</details>

**Question #2**

For each function, pick the parameter type, `String` or `&str`, and say why:

a) `fn shout(message: ???)` — prints the message followed by three exclamation marks
b) `fn label_width(label: ???)` — returns the label's `len()` for a layout calculation

<details class="solution">
<summary>Show solution</summary>

Both are `&str`, and for the same reason: each function only needs to *look at* the text. The best-practice rule from this lesson decides every case in this chapter (and most cases after it): parameters that read text take `&str`. Cases that justify a `String` parameter exist, but they involve the function *keeping* the text, which is ownership business for chapter 8.

</details>

**Question #3**

Predict the compiler's reaction, then give the one-character fix:

```rust
fn cheer(team: &str) {
    println!("Go {team}!");
}

fn main() {
    let team = String::from("Ferris");
    cheer(team);
}
```

<details class="solution">
<summary>Show solution</summary>

`error[E0308]: mismatched types` at the call: expected `&str`, found `String`, with the compiler's own help line offering the fix: `consider borrowing here`, i.e. `cheer(&team);`. One `&`, and the function gets its view.

</details>

Both string types are now on the table, which means the chapter can pay its biggest pile of IOUs: the formatting machinery that turns values *into* Strings, owed since lesson 2.4 and teased in four separate chapter 4 lessons. Next.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
