Ownership and Borrowing

One owner, moves versus copies, shared and mutable borrows, slices, and when Drop frees the value.

Ownership and borrowing

Read this lesson twice. You already know a binding, a String on the heap, and that passing a String into a function can take it away. These four sections are the rules, in order: one owner, then a borrow, then a slice, then Copy, Clone, and Drop.

One owner

RuleWhat it means
One ownerEach value has exactly one owner.
DropWhen the owner leaves scope, the value is dropped and the memory is freed.
MoveThere is only one owner at a time. A move transfers that ownership.
aownerbnew owner"hi"one heap buffer
Statusa owns the buffer

let a = String::from("hi") is one owner and one heap allocation.

A block ends the owner. Inside { let s = String::from("temp") } the heap is freed at the closing brace. That cleanup is deterministic. There is no manual free and no garbage-collector pause. Drop is the name of that cleanup. The last section of this lesson shows it on files and locks.

A function parameter that takes String takes ownership. After take(s), s is gone. A parameter that takes i32 copies, and the caller still has the number. A function can give ownership back: fn into_upper(s: String) -> String. At every boundary, ask who owns the data. Taking String is a strong contract and a stiff API. Borrowing, next, is what callers usually want.

01_ownership_rules.rsRust
fn consume(s: String) {
    println!("owned: {s}");
}

fn main() {
    let a = String::from("hi");
    let b = a;
    println!("{b}");
    let s = String::from("x");
    consume(s);
    let n = 1;
    let m = n;
    println!("{n} {m}");
}

Borrows

A borrow is temporary access that does not take ownership. &T is a shared read. &mut T is an exclusive write.

At one momentAllowed
Many shared references, &TYes
Exactly one &mut TYes
Shared and mutable at once, on the same dataNo
A reference that outlives the dataNo. The lifetime syntax is a later lesson. The rule starts now.
dataone owner&read&read&mutwrite
StatusShared reads are fine together

Several & references can look at the same value. Nobody is changing it, so they cannot race.

fn len(s: &String) -> usize { s.len() } borrows. After let n = len(&s) the owner s is still usable. fn push_bang(s: &mut String) needs let mut s and the call push_bang(&mut s). Method calls often follow the reference for you. When you assign through a mutable reference to a Copy value, write the star: let r = &mut x; *r += 1.

Prefer &str over &String on a parameter. &str accepts a literal and an owned string: greet("Ada") and greet(&owned). A String is the owner: pointer, length, and capacity, plus the heap bytes. An &str is two words, a pointer and a length, aimed at some UTF-8. &String can become &str automatically.

The caller needsThe parameter takes
A read&T or &str
A change in place&mut T
To store the value past the callT, by value
To keep a new valueReturn an owned T. Return a reference only when it is tied to something the caller already passed in.
02_borrowing_references.rsRust
fn greet(name: &str) {
    println!("hi {name}");
}

fn add_exclaim(s: &mut String) {
    s.push('!');
}

fn main() {
    let mut owned = String::from("Ada");
    greet(&owned);
    add_exclaim(&mut owned);
    greet("literal");
    let mut x = 5;
    let r = &mut x;
    *r += 1;
    println!("{owned} {x}");
}

Slices

A slice is a reference to a contiguous sequence. It does not own that sequence. &[T] is a slice of T. &str is a slice of UTF-8 bytes, and it is always valid UTF-8.

A B C D Eowner&[1..4]B C Dview
StatusThe owner holds every element

The array or string is the owner. Indexes run from the start of that sequence.

On a string, &s[0..5] and &s[..] are slices. Do not cut in the middle of a multibyte character. That panics. Walk characters, or use a Unicode library, when the text is not ASCII. fn sum(xs: &[i32]) accepts an array, a subrange, and later a growable list, because each of those can lend a slice. &str and &[T] are two words, pointer plus length, so they do not need a terminator the way a C string does.

03_slices.rsRust
fn sum(xs: &[i32]) -> i32 {
    let mut total = 0;
    for n in xs {
        total += n;
    }
    total
}

fn first_word(s: &str) -> &str {
    for (i, b) in s.bytes().enumerate() {
        if b == b' ' {
            return &s[..i];
        }
    }
    s
}

fn main() {
    let xs = [10, 20, 30, 40];
    println!("{} {}", sum(&xs[1..3]), first_word("hello rust"));
}

Copy, Clone, and Drop

TraitWhen it runsWhat it costs
CopyImplicitly, on assign and on passA bitwise duplicate. Integers, floats, bool, char, shared references &T, and tuples or arrays made only of those. A type that owns a heap buffer must not be Copy.
CloneOnly when you write value.clone()An explicit duplicate. Cloning a String allocates another buffer. derive(Clone) writes it when a bit copy is not enough.
DropWhen the owner leaves scope, including an early return and a panic unwindCleanup. A file, a socket, or a lock closes here. There is no finally block for that.
open fileacquireuse itin scopeDropclose
StatusAcquire when you create the owner

Opening a file, a socket, or a lock is tied to a value. That pairing is called RAII.

derive(Debug, Clone, Copy, PartialEq, Eq, Hash) on struct UserId(u64) is legal because the only field is Copy. The attribute still starts with a hash in real source. Derive Copy only when every field is Copy and a bit copy is the right meaning. Prefer a borrow when two places need to look at data. Clone when they each need an owned copy. Shared ownership, several owners of one allocation, is a later lesson. Do not reach for it to dodge a borrow.

Locals drop in reverse order of declaration. You rarely call drop yourself. std::mem::drop(x) moves the value and drops it early. A lock guard should unlock in Drop, so forgetting to unlock is not a path in the code.

04_copy_clone_drop.rsRust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct UserId(u64);

#[derive(Clone)]
struct Session {
    token: String,
}

fn main() {
    let id = UserId(7);
    let id2 = id;
    let session = Session { token: String::from("abc") };
    let copy = session.clone();
    println!("{:?} {:?}", id, id2);
    println!("{}", copy.token);
}