Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Ownership and Borrowing  ›  Capstone Project

Borrowing, References and Slices

Borrowing, References and Slices 15 minA reference lets code use a value without owning it, and at any moment you may have either many shared references or exactly one mutable reference, never both.

Borrowing, References and Slices

Moving a value into every function that needs to look at it would be exhausting: you would have to hand it back each time. Real libraries lend books out and get them back. Rust lets code do the same with a reference, and one rule keeps the lending safe.

The rule

Many readers or one writer, never both, and never outliving the owner.

A reference &value borrows without taking ownership. When the reference is no longer used, the borrow ends and the owner still has the value.

fn title_length(title: &str) -> usize {
    title.len()
}
let title = String::from("Iron Tides");
let n = title_length(&title);   // lend it; &String turns into &str automatically
println!("{title} {n}");       // still ours

A mutable reference &mut value lets the borrower change the value. The owner must be mut too:

fn add_edition(title: &mut String) {
    title.push_str(" (2nd ed.)");
}
let mut title = String::from("Iron Tides");
add_edition(&mut title);

The borrowing rules, checked by the compiler:

  1. At any point you may have any number of & references, or exactly one &mut, not both.
  2. A reference must never outlive the value it points to.

A borrow lasts until its last use, not to the end of the block. That is why this fails:

let mut shelf = vec![String::from("Iron Tides")];
let first = &shelf[0];                          // shared borrow starts
shelf.push(String::from("Night Garden"));       // needs &mut: error[E0502]
println!("{first}");                            // shared borrow still in use

The rule is not fussiness: push may move the vector's items to a bigger heap block, and first would point at freed memory. Move the println! above the push and it compiles.

Slices are references to part of a collection. &str is a slice of text; &[T] is a slice of an array or Vec:

let week = [4, 2, 5, 3, 6, 9, 1];
let weekend: &[u32] = &week[5..7];   // [9, 1]
let first_word: &str = &title[0..4]; // "Iron"

String slice ranges are byte positions; cutting through the middle of a multi-byte character panics.

Prefer &str over &String and &[T] over &Vec<T> in parameters: they accept more kinds of input.

The shortcut

For each borrow, draw its lifespan from creation to last use. If a shared and a mutable lifespan overlap, or two mutable ones do, it will not compile.

Checking the shelf codeStep 1 of 4
  1. Shared borrow

    first is created on line 2 and last used on line 4. Its lifespan is lines 2 to 4.

Drills

Answer fast — trust the rule.

quick drill

How many &mut references to one value can be in use at the same moment?

quick drill

Does let a = &title; let b = &title; println!("{a} {b}"); compile?

quick drill

What is &week[1..3] for let week = [4, 2, 5, 3];?

quick drill

Why is a function parameter of type &str usually better than String for a function that only reads?

quick drill

In the shelf example, why does Rust refuse the push while first is still in use?

Remember

  • Many & readers or one &mut writer, and no reference may outlive its owner.
  • A borrow lasts until its last use: draw each lifespan and look for overlap.
  • Slices (&str, &[T]) borrow part of a collection; prefer them in parameters.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainReaders or one writer

Explain why this fails and how to fix it: let mut shelf = vec![String::from("Iron Tides")]; let first = &shelf[0]; shelf.push(String::from("Night Garden")); println!("{first}");. Then explain why a function that only reads a title should take &str.