Ownership
The rules at the heart of Rust: every value has one owner, assignment moves it, Copy types are copied, and values are dropped at the end of their scope — which rules out use-after-free and double free without a garbage collector.
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:
- Each value has an owner.
- There is only one owner at a time.
- 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.
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.