Rust from the ground up

Ownership

The rule that everything else in Rust rests on: every value has one owner, moving it is not copying it, and the value is cleaned up when the owner goes out of scope.

Part 4 of 12

Intermediate18 min readBack to Courses

One owner at a time

Every value in Rust has exactly one owner — the variable that holds it. When the owner goes out of scope, the value is freed, and that is the entire memory management story: allocation when a value is made, exactly one release when its owner is done.

That is why there is no garbage collector and no free to call. The compiler knows where every owner ends, so the cleanup writes itself into the program, and it cannot run twice or be forgotten.

Moves, not copies

Assigning one of the heap-owning values — a String, a Vec, a value with one inside — to a new name moves it. The new name is the owner now, and the old name is no longer usable. The compiler enforces that, so a value can never be used after it was given away.

When you want both names to keep working, say clone() — an explicit copy of the heap data. When you only want to lend the value, use a reference, which is the next chapter. Simple values like numbers and booleans are copied instead of moved, because copying them is all there is to do.

Why this is worth it

The three classic bugs of manual memory management — freeing twice, using something after it was freed, forgetting to free — are all the same mistake: two names and one value, with no rule about who is responsible. Ownership is that rule, checked while compiling.

You will feel the strictness most in the first week, when passing a String to a function takes it away from you. The fix is usually a reference, and it is usually the design you wanted anyway: the function reads the text; it has no business owning it.

A move you can watch

fn shout(text: String) {
    println!("{}!!!", text.to_uppercase());
} // text is dropped here — the function owned it

fn main() {
    let title = String::from("hello world");

    shout(title);          // the String moves into shout
    // println!("{}", title);  // <- this would not compile: title is gone

    let again = String::from("hello world");
    let copy = again.clone();  // two owners, two values
    shout(copy);
    println!("still mine: {}", again);
}

Give it away, keep a copy

Run it and read the order of the output. Then uncomment the commented println! and note what the compiler says — then fix it by cloning before the call instead.

Output
Click "Run" to execute your code

Reading a moved value is the single most common Rust error while learning: "value borrowed here after move". It is not a warning you can ignore — it is the bug that this language exists to prevent, and the fix (a reference, or a clone) is a decision about your design.

Scope decides when a value is freed

A value lives from the line that creates it to the end of the block it was created in. That block is the scope, and the compiler inserts the cleanup at its close — not at the end of the program, and not when the name is last read. A value declared inside an if is gone when the branch ends; a value declared at the top of main lasts until main returns.

Calling drop(value) ends it early, on purpose. Shadowing does not: let name = ...; let name = ...; creates a second value while the first is still alive to the end of the block, which is why shadowing a large value keeps both allocations until that block ends.

Copy versus move

Not every assignment moves. Types whose values are cheap and self-contained — numbers, bool, char, and tuples of those — implement Copy: assigning them duplicates the bits and both names stay usable. A String is three words pointing at heap text, so copying those words would give two owners of one allocation; Rust moves instead, and retires the old name.

The same distinction decides what a function call does. Passing a Copy type hands over a copy; passing a Vec or a String hands over ownership. Returning a value moves it back out, which is why a function can take a String, change it, and give it back without a clone.

Ownership is per value, not per variable, and a struct makes that visible. Moving one field out of a struct — passing person.name to a function that takes a String — leaves the struct partially moved: the other fields are still usable, but the struct as a whole is not, and the compiler will say so if you try to print it. Moving the whole struct moves every field at once. The rule is the one from the top of the chapter, applied to a smaller unit: one owner per value, and a struct is several values side by side.

Three ownership errors you will meet this week. Reading a value after passing it by value is "borrow of moved value". Moving a value inside a loop body is "value moved in a previous iteration" — clone it, borrow it, or build a fresh one each time. And returning &local hands back a reference to a value that dies with the function; return the value, or take a reference in.

Try these next

  • Write a function that takes a String, appends a suffix, and returns it; call it twice, rebinding the result.
  • Give a struct a String field, pass the struct to a function by value, then print it after the call and read the compiler message.
  • Replace a clone() with a move, then with a reference, and decide which one the function actually needed.

A ticket that changes hands

Run it and follow the holder from line to line. Then add a third transfer and a function that takes the ticket by reference, so the caller keeps it.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

How many owners can a value have at one time?