Why ownership

Every program must decide when memory on the heap can be given back. C leaves it to the programmer, who calls free and must get it right on every path; mistakes become leaks, use-after-free and double free (see Dynamic Memory in C). Python, Java and C# use a garbage collector that frees what is no longer reachable, at the cost of a runtime that decides when. Rust takes a third way, sketched in ownership in Rust: a small set of rules that the compiler checks, so that it knows at every point in the program who is responsible for each value, and inserts the code that frees it.

The C convention of memory ownership, "exactly one part of the program frees each block", is the same idea. The difference is that in C it lives in comments, and in Rust it is part of the language, checked on every build.

The ownership rules

Ownership in Rust comes down to three rules:

  1. Each value has an owner.
  2. There is only one owner at a time.
  3. When the owner goes out of scope, the value is dropped: its clean-up code runs and its memory is freed.

The owner is usually a variable, but it can also be another value: a Vec owns its elements, and a struct owns its fields. A variable's scope ends at the closing brace of the block in which it was declared:

fn main() {
    let home = String::from("h0017");
    {
        let meter = String::from("M-30004");
        println!("{home} has meter {meter}");
    } // meter goes out of scope here: its memory is freed
    println!("{home} is still here");
} // and home here
h0017 has meter M-30004
h0017 is still here

Nothing in the code frees anything, and nothing has to run later to find out what can be freed: the compiler inserted the clean-up at the two closing braces. Using meter after its block would not compile, because the name no longer exists there.

What a String looks like in memory

Ownership matters for values that manage something outside themselves, most often heap memory. A String is a small fixed-size value with three parts: a pointer to the text on the heap, the length of the text in bytes, and the capacity, the number of bytes reserved there. The text itself can grow, so it cannot live inside the fixed-size part:

fn main() {
    let mut meter = String::from("M-30004");
    println!("text {meter:?}: length {}, capacity {}", meter.len(), meter.capacity());
    meter.push_str(" (solar)");
    println!("text {meter:?}: length {}, capacity {}", meter.len(), meter.capacity());
    println!("String itself: {} bytes", size_of::<String>());
}
text "M-30004": length 7, capacity 7
text "M-30004 (solar)": length 15, capacity 15
String itself: 24 bytes

The 24 bytes are three 8-byte values on a 64-bit Linux machine (ARM64); on a 32-bit target they are 12. How much extra capacity a String reserves when it grows is an implementation detail that you should not rely on. What matters is the split: a small header that lives wherever the variable lives (here on the stack), and heap memory that the header owns. A Vec has the same three parts.

Moves

What should let moved = meter; do with such a value? Copying only the 24-byte header would leave two headers pointing to the same heap bytes, and both would free them at the end of their scope: a double free. Copying the heap bytes too would make every assignment quietly expensive. Rust does neither. Move semantics means that assigning a value, passing it to a function or returning it transfers ownership: the header is copied to the new place, the heap data stays where it is, and the old variable is no longer usable. There is still one owner, so the text is freed exactly once.

A String on the stack and its text on the heap, before and after a move. Before: the variable meter holds a pointer, length 7 and capacity 7, and the pointer leads to the seven bytes M-30004 on the heap. After let moved = meter: the variable moved holds the same pointer, length and capacity and points to the same heap bytes, which were not copied. The variable meter is marked as moved out and can no longer be used. 1. let meter = String::from("M-30004"); meter ptr len 7 cap 7 M - 3 0 0 0 4 heap: 7 bytes 2. let moved = meter; meter moved out moved ptr len 7 cap 7 M - 3 0 0 0 4 the same 7 bytes, not copied stack
A move copies the 24-byte header and invalidates the old name. The heap data neither moves nor is copied.

Using the old name after the move is a compile error:

fn main() {
    let meter = String::from("M-30004");
    let moved = meter;
    println!("{moved}");
    println!("{meter}");
}
example.rs:5:16: error[E0382]: borrow of moved value: `meter`: value borrowed here after move

The full diagnostic tells the whole story: where the value was created, why it moved (String is not Copy), where it moved, where it was used afterward, and one way to fix it:

error[E0382]: borrow of moved value: `meter`
 --> example.rs:5:16
  |
2 |     let meter = String::from("M-30004");
  |         ----- move occurs because `meter` has type `String`, which does not implement the `Copy` trait
3 |     let moved = meter;
  |                 ----- value moved here
4 |     println!("{moved}");
5 |     println!("{meter}");
  |                ^^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let moved = meter.clone();
  |                      ++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0382`.

"Borrowed" in the message refers to println!, which reads its arguments through references (see Borrowing and References). rustc --explain E0382 prints a longer explanation with examples, and works offline. A move is cheap: whatever the size of the text, it copies 24 bytes, and the compiler often removes even that.

Moves into and out of functions

Passing a value to a function parameter moves it in exactly the same way. The function becomes the owner, and unless it gives the value back, it is dropped when the function returns:

fn register(meter: String) {
    println!("registered {meter}");
} // meter is dropped here

fn main() {
    let meter = String::from("M-30004");
    register(meter);
    println!("{meter} is registered");
}
example.rs:8:16: error[E0382]: borrow of moved value: `meter`: value borrowed here after move

For this case the compiler adds a second suggestion, which is usually the better one:

note: consider changing this parameter type in function `register` to borrow instead if owning the value isn't necessary
 --> example.rs:1:20
  |
1 | fn register(meter: String) {
  |    --------        ^^^^^^ this parameter takes ownership of the value
  |    |
  |    in this function

A function that only reads the text should take &str and borrow it, which is the subject of the next page. Taking ownership is right when the function keeps the value, for example by storing it in a list, or transforms it and returns the result. Returning a value moves it out to the caller:

fn normalized(mut meter: String) -> String {
    meter.make_ascii_uppercase();
    meter                           // ownership goes back to the caller
}

fn main() {
    let typed = String::from("m-30004");
    let meter = normalized(typed);  // typed is moved in, the result moved out
    println!("{meter}");
}
M-30004

No text was copied: normalized changed the bytes in place, in the heap memory that typed allocated, and handed the same String back. The mut before the parameter makes the function's own variable mutable; the caller's variable did not need to be.

Moves in loops

A move inside a loop body would happen on every iteration, and after the first one there is nothing left to move. The compiler says so:

fn main() {
    let meters = vec![String::from("M-30004"), String::from("M-30007")];
    let mut registered = Vec::new();
    let note = String::from("pilot");
    for meter in meters {
        registered.push(note);
        println!("{meter}");
    }
    println!("{}", registered.len());
}
example.rs:6:25: error[E0382]: use of moved value: `note`: value moved here, in previous iteration of loop

Push note.clone() to give each entry its own copy. Notice also for meter in meters: looping over a Vec by value moves the whole vector into the loop, which hands out each element as an owned value; afterward meters is gone. Loop over &meters to only look at the elements.

Moving out of a collection

You cannot move one element out of a Vec by indexing, because that would leave a hole that the vector would still try to free later:

fn main() {
    let meters = vec![String::from("M-30004"), String::from("M-30007")];
    let first = meters[0];
    println!("{first}");
}
example.rs:3:17: error[E0507]: cannot move out of index of `Vec<String>`: move occurs because value has type `String`, which does not implement the `Copy` trait

The full message suggests two fixes: borrow the element (&meters[0]) or clone it. A third is to take the element out with a method that keeps the vector consistent:

fn main() {
    let mut meters = vec![String::from("M-30004"), String::from("M-30007")];
    let first = meters[0].clone();             // a copy; meters is unchanged
    let removed = meters.remove(1);            // take it out: meters is shorter
    println!("{first} {removed} {meters:?}");
    for meter in meters {                      // the loop takes ownership of meters
        println!("owned in loop: {meter}");
    }
}
M-30004 M-30007 ["M-30004"]
owned in loop: M-30004

Copy types

Integers do not behave like String: after let copy = kwh; both variables are usable. The Copy trait marks types whose values can be duplicated by copying their bytes, with no heap memory or other resource to manage, so assignment copies them instead of moving them. The integer and floating-point types, bool, char, shared references (&T), and tuples and arrays of Copy types are all Copy. Your own struct can be too, if all its fields are, by deriving it:

#[derive(Debug, Clone, Copy)]
struct Watts(u32);

fn main() {
    let kwh: u32 = 350;
    let copy = kwh;                 // u32 is Copy: both stay usable
    println!("{kwh} and {copy}");

    let power = Watts(1234);
    let same = power;               // Copy because of the derive above
    println!("{} W and {} W", power.0, same.0);

    let reading = ("M-30004", 350); // a tuple of Copy types is Copy
    let again = reading;
    println!("{reading:?} and {again:?}");
}
350 and 350
1234 W and 1234 W
("M-30004", 350) and ("M-30004", 350)

The tuple contains "M-30004", a string literal of type &str: a reference to text that is built into the program, which is Copy because copying the reference does not copy or own the text. A type that owns heap memory can never be Copy, and the compiler refuses the derive:

#[derive(Clone, Copy)]
struct Reading {
    meter: String,
    kwh: u32,
}

fn main() {
    let reading = Reading { meter: String::from("M-30004"), kwh: 350 };
    let copy = reading;
    println!("{} {} / {} {}", reading.meter, reading.kwh, copy.meter, copy.kwh);
}
example.rs:2:8: error[E0204]: the trait `Copy` cannot be implemented for this type

The full message points at the culprit: meter: String, "this field does not implement Copy". Making a small, plain-data type Copy is convenient; making it Copy later is easy, but removing Copy from a public type breaks every caller that relied on it.

Clone: an explicit copy

Clone is the standard trait for making a duplicate of a value by calling .clone(); for most types, such as String and Vec, that includes the heap data it owns (the reference-counted pointers on Smart Pointers are the exception: their clone shares the data). Cloning a Vec<u32> allocates a new buffer and copies every element; afterward the two vectors have nothing to do with each other:

fn main() {
    let readings = vec![350, 412, 120];
    let mut corrected = readings.clone();   // a second, independent Vec
    corrected[2] = 121;
    println!("original {readings:?}");
    println!("corrected {corrected:?}");
}
original [350, 412, 120]
corrected [350, 412, 121]

The difference between Copy and Clone is about visibility of cost. A Copy is always a cheap byte copy and happens silently. A clone may allocate and copy a lot, so Rust makes you write it. When you see .clone() in a review, you know a copy happens there. Assignments and function calls never copy heap data behind your back; every such copy is a call you can see, such as .clone(), to_string() or to_vec().

Drop: clean-up at the end of a scope

When a value is dropped, Rust runs its clean-up code. For a String or Vec that means freeing the heap memory; for a file it means closing it. The Drop trait is how a type defines that code: its single method, drop, runs automatically when a value of the type goes out of scope. Variables are dropped in the reverse order of their declaration, and the function drop(value) takes ownership of a value to drop it early:

struct Connection {
    meter: String,
}

impl Drop for Connection {
    fn drop(&mut self) {
        println!("closing connection to {}", self.meter);
    }
}

fn open(meter: &str) -> Connection {
    println!("opening connection to {meter}");
    Connection { meter: String::from(meter) }
}

fn main() {
    let first = open("M-30004");
    let second = open("M-30007");
    let third = open("M-30011");
    drop(second);                   // closed now, not at the end
    println!("main ends with {} and {} open", first.meter, third.meter);
}
opening connection to M-30004
opening connection to M-30007
opening connection to M-30011
closing connection to M-30007
main ends with M-30004 and M-30011 open
closing connection to M-30011
closing connection to M-30004

Reverse order is the safe order: a value declared later may depend on one declared earlier, so it goes first. After Connection::drop has run, Rust drops the fields too, which frees the meter string. You never call the drop method yourself; connection.drop() is error E0040, "explicit use of destructor method", and the compiler suggests drop(connection) instead. That function is nothing special: it takes the value by move and lets it go out of scope.

struct Connection {
    meter: String,
}

impl Drop for Connection {
    fn drop(&mut self) {
        println!("closing connection to {}", self.meter);
    }
}

fn open(meter: &str) -> Connection {
    println!("opening connection to {meter}");
    Connection { meter: String::from(meter) }
}

fn main() {
    let _ = open("M-30004");        // no name: dropped at once
    let _kept = open("M-30007");    // a name starting with _: dropped at the end
    println!("main ends");
}
opening connection to M-30004
closing connection to M-30004
opening connection to M-30007
main ends
closing connection to M-30007

Drop during a panic

Values are also dropped when a function is left because of a panic (see Error Handling). The panic message is printed first, then, while the stack unwinds, the clean-up code of every live value runs. This example shows standard output and standard error together, in the order a terminal shows them:

struct Connection {
    meter: String,
}

impl Drop for Connection {
    fn drop(&mut self) {
        println!("closing connection to {}", self.meter);
    }
}

fn main() {
    let _connection = Connection { meter: String::from("M-30004") };
    println!("connected to M-30004");
    let readings: Vec<u32> = Vec::new();
    println!("latest: {}", readings[0]);
}
connected to M-30004

thread 'main' (48213) panicked at example.rs:15:36:
index out of bounds: the len is 0 but the index is 0
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
closing connection to M-30004

The program exits with status 101 after the clean-up. A build can be configured to abort on a panic instead of unwinding (the Cargo profile setting panic = 'abort', sometimes used for small binaries and firmware); then no Drop code runs, and the operating system reclaims the process's memory.

Scope-based resource management

Scope-based resource management ties a resource, such as memory, an open file, a network connection or a held lock, to the lifetime of a value: acquiring the resource creates the value, and dropping the value releases it. C++ calls the same pattern RAII (resource acquisition is initialization). In Rust it is how the whole standard library works: a File is closed when it is dropped, a MutexGuard releases its lock (see Concurrency), and a Vec frees its buffer. There is no close() to forget on an error path, because every way out of the scope, a normal return, an early return, ? or a panic, runs the same clean-up (only ending the whole process, with std::process::exit or an aborting panic, skips it). Compare the C pattern of a create/destroy pair that every caller must use correctly, on Dynamic Memory.

What ownership prevents

The rules exist to make whole classes of memory bugs impossible in safe Rust, at compile time, without slowing the program down:

Bug In C In safe Rust
Double free two copies of a pointer both call free one owner; the compiler inserts exactly one drop
Use-after-free a pointer is used after free a moved or dropped value cannot be named (E0382), and a reference cannot outlive its value (see Lifetimes)
Forgotten free on an error path a memory leak every exit from a scope drops what it owns
Forgotten fclose or unlock a leaked file handle or a held lock the resource is released by Drop

It is worth being precise about the limits. Ownership does not make leaks impossible: a value can be kept alive on purpose (std::mem::forget, Box::leak) or by accident, for example in a cycle of reference-counted pointers (see Smart Pointers), and Rust's reference documents leaks as "not considered unsafe". A value kept in a list that only grows is a leak in any language. Ownership also says nothing about logic errors, and code inside unsafe blocks must uphold the rules itself (see Unsafe Rust and Calling C). What it does guarantee is that safe code cannot free memory twice or use memory after it was freed, which is why Rust counts as a memory-safe language.

Moving values in and out of every function would be clumsy, and it is not how most Rust code looks. Most functions borrow what they only need to read or change, which is the subject of Borrowing and References.