Lesson 7 of 22·17 min

Ownership and Borrowing

Every program has to answer the same question: when is a piece of memory no longer needed, so it can be given back? Some languages leave it to you, and a forgotten or doubled free becomes a crash or a security hole. Others run a garbage collector that finds unused memory while the program runs.

Rust does neither. It answers the question at compile time with a small set of rules called ownership. This lesson covers those rules, what happens when you assign or pass a value, how to use a value without taking it with references, and why Rust has two string types, String and &str.

Ownership is the idea that makes Rust feel different from other languages, and the compiler errors it produces are the ones beginners meet most. Take your time with this one.

The ownership rules

There are three of them:

  1. Every value has an owner, the variable that holds it.
  2. A value has exactly one owner at a time.
  3. When the owner goes out of scope, the value is dropped and its memory is freed.

A scope is the block a variable lives in, from its let to the closing brace:

fn main() {
    let outer = String::from("outer");
    {
        let inner = String::from("inner");
        println!("{outer} and {inner}");
    } // `inner` goes out of scope here, and its String is dropped
 
    println!("{outer} is still here");
} // `outer` is dropped here
outer and inner
outer is still here

Rust inserts the cleanup at the closing brace for you. There is no free to forget, and nothing can free the same memory twice, because only the one owner does it.

String owns its text

The examples use String because it is the simplest type where ownership matters. A String is a growable piece of text. Its characters live in memory on the heap, which is allocated while the program runs, and the String variable itself holds a pointer to that memory, a length and a capacity.

fn main() {
    let mut title = String::from("Rust");
    title.push_str(" in Action");
    println!("{title} has {} bytes", title.len());
}
Rust in Action has 14 bytes

Integers, floats, bool and char are different: they have a fixed, small size, so the whole value is stored directly in the variable. That difference explains the next two sections.

Moves

What happens when you assign a String to another variable?

fn main() {
    let first = String::from("ferris");
    let second = first;
    println!("{second}");
}
ferris

Only the pointer, length and capacity are copied. The text on the heap is not, so now two variables would point at the same memory. If both were still valid, both would free it at the end of main. Rust prevents that by treating the assignment as a move: ownership passes to second, and first can no longer be used. Using it afterwards is a compile error, shown in the common mistakes below.

Passing a value to a function moves it too. The parameter becomes the new owner, and the value is dropped when the function returns:

fn announce(name: String) {
    println!("Now playing: {name}");
} // `name` is dropped here
 
fn main() {
    let song = String::from("Crab Rave");
    announce(song);
    // `song` was moved into `announce` and can't be used here
}
Now playing: Crab Rave

A function can hand ownership back by returning the value:

fn add_exclamation(mut text: String) -> String {
    text.push('!');
    text
}
 
fn main() {
    let cheer = String::from("Go");
    let cheer = add_exclamation(cheer);
    println!("{cheer}");
}
Go!

This works, but passing everything in and back out would get tiring fast. References, a few sections down, are the real answer.

Cloning

When you do want two independent copies of heap data, ask for one with .clone():

fn main() {
    let original = String::from("draft");
    let mut copy = original.clone();
    copy.push_str(" v2");
    println!("{original} / {copy}");
}
draft / draft v2

clone allocates new memory and copies the text into it, so each variable owns its own String. It is explicit on purpose: when you see .clone(), you know work is being done. Cloning to silence the compiler is a common beginner habit; before reaching for it, check whether a reference would do.

Copy types

Simple values don't move. They are copied, and both variables stay usable:

fn main() {
    let a = 42;
    let b = a;
    let point = (1.5, -2.0);
    let moved_point = point;
    println!("{a} {b} {point:?} {moved_point:?}");
}
42 42 (1.5, -2.0) (1.5, -2.0)

Types that behave like this implement the Copy trait. All the integer and float types, bool and char are Copy, and so are tuples and arrays made only of Copy types. Copying them is as cheap as moving would be, and they own no heap memory, so there is nothing to free twice. That is why the countdown example in the functions lesson could change its parameter without touching the caller's variable.

String is not Copy, and neither is anything that contains one.

References and borrowing

Often a function only needs to look at a value. Instead of moving the value in, pass a reference to it with &. The function borrows the value; the caller keeps ownership:

fn count_vowels(text: &String) -> usize {
    let mut count = 0;
    for c in text.chars() {
        if "aeiouAEIOU".contains(c) {
            count += 1;
        }
    }
    count
}
 
fn main() {
    let phrase = String::from("Borrow checker");
    let vowels = count_vowels(&phrase);
    println!("{phrase} has {vowels} vowels");
}
Borrow checker has 4 vowels

The parameter type &String means "a reference to a String", and the call passes &phrase. Because count_vowels never owned the String, nothing is dropped when it returns, and phrase is still usable afterwards. A reference is just a pointer to the value, so making one is cheap.

Methods borrow the same way. phrase.len() borrows phrase for the length of the call, which is why you can keep using phrase after calling it.

A reference is read-only by default. Trying to change the borrowed value through it doesn't compile.

Mutable references

To let a function change a value it doesn't own, pass a mutable reference, &mut. The variable itself must be declared mut:

fn add_bonus(score: &mut u32) {
    *score += 10;
}
 
fn shorten(text: &mut String) {
    text.truncate(5);
}
 
fn main() {
    let mut score = 75;
    add_bonus(&mut score);
 
    let mut headline = String::from("Breaking news");
    shorten(&mut headline);
 
    println!("{score} {headline}");
}
85 Break

The * in *score += 10 dereferences the reference: it means "the value score points to", not the reference itself. Method calls like text.truncate(5) dereference automatically, so you rarely write * with them.

The &mut shows up at both ends, in the signature and at the call. Anyone reading shorten(&mut headline) can tell that headline may change.

The borrowing rules

References come with two rules, checked by the part of the compiler called the borrow checker:

  1. At any moment you can have either one mutable reference or any number of shared (&) references to a value, but not both.
  2. A reference must never outlive the value it points to.

Many readers or one writer. As long as nobody can change a value, any number of readers can look at it safely. While someone is changing it, nobody else may read or write it, or they might see it half-changed:

fn main() {
    let mut level = String::from("easy");
 
    let a = &level;
    let b = &level;
    println!("{a} {b}");
 
    let c = &mut level;
    c.push_str(" mode");
    println!("{c}");
}
easy easy
easy mode

This compiles even though c is created while a and b are still in scope. A borrow lasts from where the reference is created to where it is last used, not to the end of the block. a and b are last used in the first println!, so by the time c borrows level mutably, they are finished.

If a were used again after c was created, the borrows would overlap and the code would not compile. When the compiler complains about overlapping borrows, the fix is usually to finish with one reference before creating the other, or to limit a reference to a smaller block.

The owner is bound by the rules as well. While a shared reference is in use, even the owner can't change the value, and while a mutable reference is in use, the owner can't read it.

The second rule stops dangling references, pointers to memory that has already been freed. A function can't return a reference to a local variable, because the local is dropped when the function returns. Return the owned value instead.

String and &str

Rust has two main string types, and ownership is the difference between them:

  • String owns its text on the heap. It can grow and shrink, and it is freed when its owner goes out of scope.
  • &str, a string slice, borrows text that some other value owns. It is a pointer and a length, and it can't change the text.

String literals like "hello" are &str. Their text is stored in the compiled program itself, so they are valid for the whole run. The slices lesson shows how to take a &str that points at part of a String.

To get a String from a &str, use String::from or .to_string(). To get a &str from a String, borrow it: &name or name.as_str().

When a function only reads text, take &str rather than &String. A &String is converted to &str automatically, so the function then accepts both:

fn initials(full_name: &str) -> String {
    let mut result = String::new();
    for word in full_name.split_whitespace() {
        if let Some(first) = word.chars().next() {
            result.push(first);
        }
    }
    result
}
 
fn main() {
    let owned = String::from("Grace Brewster Hopper");
    println!("{}", initials(&owned));
    println!("{}", initials("Ada Lovelace"));
}
GBH
AL

initials returns a String because it builds new text; returning a &str would need something to own that text. (if let and Some come in the enums and Option lessons. Here they mean "if the word has a first character, call it first".)

Bytes and characters

Rust strings are UTF-8, so one character can take between one and four bytes. len() counts bytes, not characters:

fn main() {
    let word = "naïve";
    println!("{} bytes", word.len());
    for c in word.chars() {
        print!("[{c}]");
    }
    println!();
}
6 bytes
[n][a][ï][v][e]

ï takes two bytes, so the five-character word is six bytes long. When you care about characters rather than storage, walk them with .chars(), which yields each char in turn. The closures and iterators lesson covers the many things you can do with what .chars() returns.

Common mistakes

Using a value after it moved

fn main() {
    let playlist = String::from("Road trip");
    let saved = playlist;
    println!("{playlist} is saved as {saved}");
}

The compiler points at the line that moved the value and the line that used it afterwards. Use saved instead, borrow with let saved = &playlist;, or clone if you really need two copies. Passing a String to a function that takes String moves it the same way; change the parameter to &str if the function only reads it.

Changing a value while it is borrowed

fn main() {
    let mut name = String::from("Ferris");
    let first_look = &name;
    name.clear();
    println!("{first_look}");
}

name.clear() needs a mutable borrow of name, but first_look is still used on the next line. If this compiled, first_look would show text that had just been erased. Finish using the shared reference before changing the value.

Two mutable references at once

fn main() {
    let mut total = 0;
    let first = &mut total;
    let second = &mut total;
    *first += 1;
    *second += 1;
    println!("{total}");
}

Only one mutable reference may be in use at a time. Here one is enough: change total through first twice, or skip the references and write to total directly.

Borrowing mutably from an immutable variable

fn reset(counter: &mut u32) {
    *counter = 0;
}
 
fn main() {
    let clicks = 12;
    reset(&mut clicks);
    println!("{clicks}");
}

A mutable reference can only come from a mutable variable. Declare it with let mut clicks.

Returning a reference to a local

fn make_greeting() -> &String {
    let greeting = String::from("hello");
    &greeting
}
 
fn main() {
    println!("{}", make_greeting());
}

greeting is dropped when make_greeting returns, so the reference would point at freed memory. The error talks about lifetimes, which get their own lesson later. Ignore the 'static suggestion, which is for values that live for the whole program; the second one is the fix: return the String itself, -> String and greeting without the &.

Summary

  • Every value has one owner, and it is dropped when the owner goes out of scope.
  • Assigning or passing a String (or anything else that isn't Copy) moves it; the old variable can't be used. .clone() makes an independent copy.
  • Integers, floats, bool, char, and tuples and arrays of them are Copy: assigning them copies the value.
  • &value borrows without taking ownership; &mut value borrows so the value can be changed, and needs a mut variable. * reaches the value behind a reference.
  • Either one &mut or any number of & at a time, and a borrow lasts until the reference's last use. References can't outlive their value.
  • String owns its text; &str borrows it. Take &str in functions that only read text. len() counts bytes; .chars() walks characters.

Practise what you read

Challenges that use the ideas from this lesson.