Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Ownership

Ownership is how Rust manages memory without a garbage collector. The compiler tracks which binding is responsible for each value and rejects code that could use invalid memory.

The core rules are:

  1. Every value has an owner.
  2. A value has only one owner at a time.
  3. When the owner leaves scope, the value is dropped.

References let code use a value without taking ownership of it. Rust calls creating a reference borrowing.

Values are dropped with their owners

fn main() {
    {
        let message = String::from("hello");
        println!("{message}");
    } // message leaves scope; its String is dropped here
}

String owns a heap allocation. When message leaves scope, Rust runs the value’s Drop implementation and releases that allocation. No explicit free call and no garbage collector are required.

Scope is usually marked by braces. Function parameters, local variables, and temporary values all have scopes that determine when they are dropped.

Assignment usually moves ownership

fn main() {
    let first = String::from("hello");
    let second = first;

    println!("{second}");
}

After let second = first, second owns the String. Rust calls this a move. The bytes that describe the String—a pointer, length, and capacity—are copied to second, but the heap allocation itself is not copied.

Rust invalidates first so that only one binding can release the allocation:

fn main() {
    let first = String::from("hello");
    let second = first;

    println!("{first}");
    println!("{second}");
}

The compiler reports that first was used after it was moved. This prevents a double free: if both bindings still considered themselves owners, both would try to release the same allocation.

Passing a value can move it

Function parameters are bindings, so passing an owned value transfers ownership unless its type is Copy:

fn print_length(text: String) {
    println!("{}", text.len());
}

fn main() {
    let name = String::from("Ferris");
    print_length(name);
    println!("Hello, {name}");
}

name moves into the parameter text. At the end of print_length, text leaves scope and the String is dropped. The caller no longer owns it.

A function can return ownership:

fn inspect(text: String) -> String {
    println!("{} bytes", text.len());
    text
}

fn main() {
    let name = String::from("Ferris");
    let name = inspect(name);
    println!("Hello, {name}");
}

This works, but returning every value merely to preserve it is awkward. Borrowing is the usual solution.

Borrow with &T

A shared reference, written &T, provides temporary read access to a T:

fn print_length(text: &String) {
    println!("{}", text.len());
}

fn main() {
    let name = String::from("Ferris");

    print_length(&name);
    println!("Hello, {name}");
}

&name borrows the value instead of moving it. The parameter text owns only the reference, not the String. When print_length returns, the borrow ends and the caller still owns name.

The type shows the ownership behavior:

String   owned string value
&String  shared borrow of a String

A shared reference cannot mutate the borrowed value:

#![allow(unused)]
fn main() {
fn add_mark(text: &String) {
    text.push('!');
}
}

That restriction makes it safe to have multiple shared references at the same time:

fn main() {
    let name = String::from("Ferris");
    let first = &name;
    let second = &name;

    println!("{first} and {second}");
}

Both references only read name, so neither can interfere with the other.

The next chapter will improve &String parameters to &str, which accepts a wider range of string data. For now, the important distinction is between the owned value and a reference to it.

Mutate through &mut T

The owner must first allow mutation with mut. A mutable reference, written &mut T, then grants temporary read and write access:

fn add_mark(text: &mut String) {
    text.push('!');
}

fn main() {
    let mut message = String::from("hello");

    add_mark(&mut message);
    println!("{message}");
}

While a mutable borrow is in use, no other borrow of that value may be used:

fn main() {
    let mut message = String::from("hello");
    let borrowed = &message;

    message.push('!');
    println!("{borrowed}");
}

borrowed points into message and is used by the final println!. Mutating message before that use conflicts with the active shared borrow.

The practical borrowing rule is:

any number of shared references (&T)
                   or
exactly one mutable reference (&mut T)

This prevents data from changing while other code is reading it and prevents two writers from changing it at the same time.

A borrow lasts until its final use

A borrow does not always last until the closing brace. It usually ends after the reference’s final use:

fn main() {
    let mut message = String::from("hello");
    let borrowed = &message;

    println!("before: {borrowed}");
    message.push('!');
    println!("after: {message}");
}

The shared borrow ends after the first println!, so the later mutation is allowed. This is called a non-lexical lifetime: the compiler follows actual uses rather than blindly extending every borrow to the end of its block.

Some types are copied instead of moved

Simple, fixed-size values commonly implement the Copy trait:

fn main() {
    let first = 10;
    let second = first;

    println!("{first} {second}");
}

Assignment copies the i32, so both bindings remain usable. Types such as integers, floating-point numbers, booleans, characters, and shared references are Copy.

Types that manage resources, such as String, Vec<T>, and files, are not Copy. Silently duplicating them would make ownership of the underlying resource ambiguous or require hidden expensive work.

Copy is implicit. Clone is explicit:

fn main() {
    let first = String::from("hello");
    let second = first.clone();

    println!("{first} {second}");
}

clone creates a second independently owned String and copies the heap data. Use it when two owners are genuinely needed, not simply to avoid reasoning about a move.

Moving one field can partially move a value

Ownership is tracked precisely enough to move a field out of a struct:

struct User {
    name: String,
    level: u32,
}

fn main() {
    let user = User {
        name: String::from("Ferris"),
        level: 3,
    };

    let name = user.name;

    println!("{name}");
    println!("{}", user.level);
}

The String moves out of user.name, while the Copy field user.level remains usable. The complete user value can no longer be used because it is missing a field.

Borrow the field instead when the complete struct must remain intact:

struct User {
    name: String,
    level: u32,
}
fn main() {
let user = User {
    name: String::from("Ferris"),
    level: 3,
};
let name = &user.name;
println!("{name}");
println!("{}", user.level);
}

Read ownership from function signatures

These signatures describe three different contracts:

fn store(value: String)       // takes ownership
fn inspect(value: &String)    // borrows for reading
fn modify(value: &mut String) // borrows for mutation

Use an owned parameter when the function needs to store, consume, or transfer the value. Use a shared reference when it only needs to read. Use a mutable reference when it must modify the caller’s existing value.

When a move or borrow error appears, trace the value through the program:

  1. Where was the value created?
  2. Which binding currently owns it?
  3. Did an assignment, function call, or closure move it?
  4. Which references are still used later?
  5. Does the operation need ownership, shared access, or mutable access?

Those questions usually reveal whether the correct fix is to move, borrow, shorten a borrow, or create a deliberate clone.