# References & borrowing

> Lend values with & and &mut, follow the borrowing rules, and use slices, String and &str correctly in function signatures.

Rust roadmap · Foundations · 17 min read · https://levelcode.org/roadmaps/rust/borrowing

Moving a value into every function that needs to look at it would be unbearable. Instead, you **borrow** it: you hand out a reference, and ownership stays where it was. This lesson covers shared and mutable references, the one rule the borrow checker enforces, why that rule prevents real bugs, and slices — including the `String` versus `&str` question every Rust beginner asks.

## Shared references

A reference, written `&T`, points at a value without owning it. Creating one is called **borrowing**.

```rust
fn length(s: &String) -> usize {
    s.len()
} // s goes out of scope, but it doesn't own anything, so nothing is dropped

fn main() {
    let name = String::from("Ferris");
    let n = length(&name); // lend name to the function
    println!("{name} has {n} letters"); // name is still ours
}
```

`&name` creates a reference. The function takes `&String` and reads through it. When the function returns, the reference disappears, and `name` is untouched.

You don't need to write `*s` to read through a reference most of the time: method calls and field access **auto-dereference**, so `s.len()` works on a `&String` as it would on a `String`. You do write `*` explicitly when you assign through a mutable reference or compare a reference with a plain value.

Shared references are read-only:

```rust
fn add_suffix(s: &String) {
    s.push_str("!"); // error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
}
```

## Mutable references

To modify a borrowed value, take a **mutable reference**, `&mut T`. The variable itself must be declared `mut` too:

```rust
fn add_suffix(s: &mut String) {
    s.push_str("!");
}

fn main() {
    let mut greeting = String::from("hello");
    add_suffix(&mut greeting);
    println!("{greeting}"); // hello!

    let mut count = 0;
    let r = &mut count;
    *r += 1; // explicit dereference to assign through the reference
    println!("{count}"); // 1
}
```

The `&mut` at the call site is deliberate: when you read `add_suffix(&mut greeting)`, you know `greeting` may change. Code that only borrows with `&` can't change it.

## The borrowing rule

At any moment, a value can have **either**:

- any number of shared references (`&T`), **or**
- exactly one mutable reference (`&mut T`),

but not both. And every reference must be valid: it can't outlive the value it points to.

Put shortly: **aliasing XOR mutation.** Many readers or one writer, never a writer alongside anyone else.

```rust
fn main() {
    let mut scores = vec![10, 20, 30];
    let first = &scores[0];   // shared borrow of scores
    scores.push(40);          // needs &mut scores
    println!("{first}");      // shared borrow still in use
}
```

```text
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let first = &scores[0];
  |                  ------ immutable borrow occurs here
4 |     scores.push(40);
  |     ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("{first}");
  |               ------- immutable borrow later used here
```

This isn't the compiler being fussy. `push` may need more capacity, in which case `Vec` allocates a new buffer, copies the elements and frees the old one. `first` would then point into freed memory. In C++ this exact bug (iterator invalidation) is a classic source of crashes and exploits. Rust rejects it at compile time.

The same rule prevents data races: a data race needs two accesses to the same memory with at least one write, and "one writer xor many readers" forbids exactly that. You'll see how this extends to threads in the [concurrency lesson](https://levelcode.org/roadmaps/rust/concurrency).

### Borrows end at their last use

A reference's borrow lasts until the reference is last used, not until the end of the block. This is called **non-lexical lifetimes**, and it's why the following compiles:

```rust
fn main() {
    let mut scores = vec![10, 20, 30];
    let first = &scores[0];
    println!("{first}");  // last use of first: the shared borrow ends here
    scores.push(40);      // fine: no other borrow is live
    println!("{scores:?}");
}
```

Often, fixing a borrow error just means reordering: finish reading before you start writing, or copy the value out (`let first = scores[0];` copies the `i32`, so no borrow remains at all).

> [!TIP]
> When the borrow checker complains, find the three places its message points at: where the first borrow starts, where the conflicting borrow happens, and where the first one is used later. The fix is almost always to shrink one of those borrows so they don't overlap.

## References can't dangle

```rust
fn dangle() -> &String {
    let s = String::from("temporary");
    &s
} // s is dropped here, so the reference would point at freed memory
```

This doesn't compile; the compiler reports a missing lifetime specifier and suggests returning the `String` itself. The fix is to return an owned value and let ownership move to the caller:

```rust
fn no_dangle() -> String {
    String::from("temporary")
}
```

How the compiler reasons about how long references live is the subject of the [lifetimes lesson](https://levelcode.org/roadmaps/rust/lifetimes). For now: a reference can never outlive its owner.

## Slices

A **slice** is a reference to a contiguous part of a collection. It's a "fat pointer": a pointer plus a length. `&[T]` is a slice of any array or vector of `T`:

```rust
fn sum(values: &[i32]) -> i32 {
    let mut total = 0;
    for v in values {
        total += v;
    }
    total
}

fn main() {
    let array = [1, 2, 3, 4, 5];
    let vector = vec![10, 20, 30];
    println!("{}", sum(&array));       // whole array
    println!("{}", sum(&vector));      // whole Vec: &Vec<i32> coerces to &[i32]
    println!("{}", sum(&array[1..4])); // 2 + 3 + 4 = 9
}
```

Range syntax picks the part: `&v[1..4]` is indexes 1, 2, 3; `&v[..2]` is the first two; `&v[2..]` is from index 2 to the end. Out-of-range slices panic, like indexing.

Taking `&[T]` instead of `&Vec<T>` makes a function more useful: it accepts arrays, vectors and sub-ranges of either, at no cost.

## String and &str

Rust has two main string types, and the difference is ownership.

| | `String` | `&str` |
| --- | --- | --- |
| What | An owned, growable UTF-8 buffer on the heap | A borrowed slice of UTF-8 bytes |
| Owns its data | Yes, freed on drop | No, points into someone else's data |
| Can grow | Yes (`push_str`, `push`) | No |
| Created by | `String::from("…")`, `"…".to_string()`, `format!` | String literals, `&my_string`, `&my_string[a..b]` |

A string literal like `"hello"` is a `&'static str`: a slice pointing into the program binary, valid for the whole run.

**Take `&str` in function parameters** unless you need to own or modify the string. A `&String` coerces to `&str` automatically (this is called **deref coercion**), so callers can pass either:

```rust
fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

fn main() {
    let owned = String::from("hello world");
    println!("{}", first_word(&owned));       // &String -> &str
    println!("{}", first_word("rust lang"));  // literal is already &str
}
```

**Return `String`** when you build a new string; you can't return a reference to something you created inside the function.

Because strings are UTF-8, a character can take one to four bytes. That's why `s[0]` doesn't compile: indexing by byte could split a character. Slice by byte ranges you know are on character boundaries (like those `find` returns), or iterate with `s.chars()`:

```rust
fn main() {
    let s = "héllo";
    println!("{}", s.len());             // 6 bytes: é is two bytes
    println!("{}", s.chars().count());   // 5 characters
    println!("{:?}", s.chars().nth(1));  // Some('é')
    // &s[0..2] would panic: byte 2 is in the middle of 'é'
}
```

> [!WARNING]
> Slicing a `&str` at a byte index that isn't a character boundary panics at runtime. Indexes from `find`, `char_indices` or `split` are always safe.

## Practice

1. Write `fn longest_word(text: &str) -> &str` that returns the longest whitespace-separated word (use `text.split_whitespace()`). If two words tie, return the first. Call it with both a literal and a `&String`.
2. Write `fn normalize(names: &mut Vec<String>)` that trims each name and converts it to lower case **in place**, then removes empty entries (look at `Vec::retain`).
3. Explain why this doesn't compile, then fix it two different ways:

```rust
fn main() {
    let mut names = vec![String::from("ana")];
    let first = &names[0];
    names.push(String::from("bo"));
    println!("{first}");
}
```

**Solution**

1.

```rust
fn longest_word(text: &str) -> &str {
    let mut best = "";
    for word in text.split_whitespace() {
        if word.len() > best.len() {
            best = word;
        }
    }
    best
}

fn main() {
    let owned = String::from("the quick brown fox");
    println!("{}", longest_word(&owned));          // quick
    println!("{}", longest_word("borrow checker")); // checker
}
```

The returned `&str` points into the input, which is why the function can return a reference without owning anything. `len()` counts bytes; for non-ASCII text, compare `chars().count()` instead.

2.

```rust
fn normalize(names: &mut Vec<String>) {
    for name in names.iter_mut() {
        *name = name.trim().to_lowercase();
    }
    names.retain(|n| !n.is_empty());
}
```

`iter_mut` yields `&mut String`, so `*name = …` replaces each string in place. `retain` keeps elements for which the closure returns `true`.

3. `first` is a shared borrow of `names`, and it's used after `push`, which needs a mutable borrow. `push` can reallocate the buffer, leaving `first` dangling.

Fix A: finish using `first` before pushing.

```rust
let first = &names[0];
println!("{first}");
names.push(String::from("bo"));
```

Fix B: take an owned copy, so no borrow remains.

```rust
let first = names[0].clone();
names.push(String::from("bo"));
println!("{first}");
```

## Key takeaways

- `&T` borrows a value read-only; `&mut T` borrows it with permission to change it. Ownership doesn't move.
- The rule: many shared references **or** one mutable reference, never both at once. It rules out iterator invalidation and data races.
- A borrow lasts until its last use, so reordering code often fixes borrow errors.
- References can never outlive what they point to; return owned values from functions that create them.
- Take `&str` and `&[T]` in parameters; return `String` and `Vec<T>` when you build something new.

## Quiz

1. Which signature is the most flexible for a function that only reads a list of numbers?

   A. fn total(v: Vec<i32>) -> i32
   B. fn total(v: &[i32]) -> i32
   C. fn total(v: &Vec<i32>) -> i32
   D. fn total(v: &mut Vec<i32>) -> i32

   **Answer: B.** &[i32] accepts arrays, vectors and sub-slices of either, without taking ownership or requiring mutability. &Vec<i32> only accepts vectors, and Vec<i32> moves the caller's data away.

2. Why does Rust reject holding &v[0] across a call to v.push(…)?

   A. push might reallocate the buffer, leaving the reference dangling
   B. Vectors can't be indexed while borrowed
   C. Shared references are only valid inside one function
   D. push takes ownership of v

   **Answer: A.** push needs &mut v, which can't coexist with a shared borrow. The underlying reason is real: growing a Vec may move its elements to a new allocation.

3. let s = String::from("día"); What is s.len()?

   A. 3
   B. 2
   C. 4
   D. It doesn't compile: Strings have no len

   **Answer: C.** len() counts bytes. 'í' takes two bytes in UTF-8, so "día" is 4 bytes and 3 characters (s.chars().count()).

4. let mut x = 5; let a = &x; println!("{a}"); let b = &mut x; *b += 1; — does it compile?

   A. No: x has a shared and a mutable borrow in the same scope
   B. No: you can't dereference a mutable reference
   C. Yes, but it panics at runtime
   D. Yes: a's borrow ends at its last use, before b is created

   **Answer: D.** Borrows are non-lexical: a is last used in println!, so its borrow is over when b takes &mut x. Nothing overlaps.
