Understanding Ownership, Moves, and Borrowing¶
Every program has to answer three questions about its data: where does it live, who is allowed to use it, and when is it cleaned up?
Garbage-collected languages (Python, Go, Java) answer the last question at runtime: a collector periodically finds values nothing refers to and frees them. C and C++ leave it largely to the programmer. Rust takes a third route called ownership: a small set of rules, checked by the compiler, that decide when memory is freed and who may touch it. Get them right and you get memory safety with no garbage collector. Get them wrong and the program doesn't compile.
This page builds the model up from nothing. If you have a specific use of partially moved value error in front of you, the companion page Fixing "use of partially moved value" walks through one in detail.
The three ownership rules¶
Everything else on this page follows from these:
- Every value has exactly one owner (a variable, a struct field, a collection slot, …).
- Ownership can be transferred (moved), but there is still only one owner at a time.
- When the owner goes out of scope, the value is dropped: its destructor runs and its memory is freed.
An analogy helps. Lending a friend a book is different from giving it away:
- Give it away → it's theirs now. That's a move.
- Lend it → you still own it; they get to use it for a while and must give it back. That's a borrow.
Scope and drop¶
A value lives until the end of the block that owns it:
fn main() {
{
let greeting = String::from("hello"); // `greeting` owns a heap buffer
println!("{greeting}");
} // <- `greeting` goes out of scope; the String is dropped and its memory freed
// println!("{greeting}"); // error[E0425]: cannot find value `greeting`
}
Dropping is deterministic. You know exactly where cleanup happens: at the closing brace. Rust uses the same mechanism for any resource, not just memory. A File closes itself, a MutexGuard unlocks itself, a database connection returns to its pool, all when the owner goes out of scope. This pattern is called RAII (Resource Acquisition Is Initialization), and it's why Rust code rarely needs finally blocks or defer.
Moving: transferring ownership¶
Assigning a value that owns resources to another variable moves it:
let first = String::from("hello");
let second = first; // ownership moves to `second`
println!("{second}"); // fine
// println!("{first}"); // error[E0382]: borrow of moved value: `first`
Why forbid using first? A String is three machine words on the stack (pointer, length, capacity) pointing at a buffer on the heap. let second = first copies those three words. If both variables stayed usable, both would think they own the buffer, and both would try to free it at the end of scope, a double free. Rust prevents that by declaring first dead after the move. No data was duplicated; responsibility changed hands.
Moves into and out of functions¶
Passing a value to a function moves it, exactly like assignment. Returning a value moves it back out:
fn shout(s: String) -> String {
s.to_uppercase()
} // `s` is dropped here (to_uppercase built a new String)
fn main() {
let word = String::from("hi");
let loud = shout(word); // `word` moved into `shout`
println!("{loud}"); // "HI"
// println!("{word}"); // error[E0382]: `word` was moved
}
Moves also happen in less obvious places:
for item in vecconsumes the vector (usefor item in &vecto borrow it).- Closures declared with
move |…|take ownership of what they capture. - Destructuring patterns (
let (a, b) = pair;,matcharms) can move fields out of a larger value. - Methods that take
self(not&self) consume the receiver, e.g.Option::unwrap,Vec::into_iter,String::into_bytes.
Borrowing: access without ownership¶
Moving a value into a function just to read it, and then moving it back, would be tedious. Instead you borrow it by creating a reference with &:
fn len_of(s: &str) -> usize {
s.len()
} // nothing is dropped here: `len_of` never owned the string
fn main() {
let name = String::from("Ada");
let n = len_of(&name); // lend `name` to the function
println!("{name} has {n} bytes"); // `name` is still usable
}
There are two kinds of reference:
| Reference | Name | What you can do |
|---|---|---|
&T |
shared (immutable) reference | Read. Any number may exist at once. |
&mut T |
exclusive (mutable) reference | Read and write. Only one may exist at a time. |
fn bump(score: &mut i32) {
*score += 1; // `*` dereferences: "the value behind this reference"
}
fn main() {
let mut score = 10; // the binding itself must be `mut` to lend it mutably
bump(&mut score);
println!("{score}"); // 11
}
The borrowing rules¶
At any given point in the program, for a given value you may have either:
- any number of shared references (
&T), or - exactly one exclusive reference (
&mut T),
but never both. And every reference must be valid: it can't outlive the value it points to.
A useful mental shorthand: aliasing XOR mutation. Data may be shared, or it may be changed, but not both at once. That single rule rules out data races, iterator invalidation, and a large family of "I changed it while someone else was reading it" bugs.
Here's the classic violation:
let mut names = vec![String::from("a"), String::from("b")];
let first = &names[0]; // shared borrow of `names`
names.push(String::from("c")); // error[E0502]: cannot borrow `names` as mutable
// because it is also borrowed as immutable
println!("{first}");
This isn't pedantry. push may reallocate the vector's buffer, which would leave first pointing at freed memory. In C++ this compiles and crashes later (sometimes); in Rust it doesn't compile.
Borrows end at their last use¶
A borrow lasts until the reference is last used, not until the end of the block. This is called non-lexical lifetimes (NLL), and it makes many apparent conflicts disappear:
let mut names = vec![String::from("a"), String::from("b")];
let first = &names[0];
println!("{first}"); // last use of `first`: the shared borrow ends here
names.push(String::from("c")); // fine: no outstanding borrows
When you hit a borrow error, a common fix is simply to reorder statements so the reference's last use comes before the conflicting operation, or to copy out the small piece of data you need (let first_len = names[0].len();) instead of holding a reference.
Copy and Clone: when the original stays usable¶
Some types are so small and simple that duplicating them is trivially cheap and can never cause a double free. These implement the Copy trait, and assignment copies them instead of moving them:
let a = 10;
let b = a; // copy, not move
println!("{a} {b}"); // both usable
Copy types include all integers and floats, bool, char, shared references &T, and tuples/arrays made only of Copy types. A type that owns heap memory or any other resource (String, Vec<T>, Box<T>, File) can never be Copy.
Your own types can opt in with #[derive(Clone, Copy)] if all their fields are Copy. Do this for small, plain-data value types (a 2D point, an ID wrapper, a colour). Don't add it just to silence a move error. Ask whether copying the type makes semantic sense first.
For everything else there's Clone, an explicit, potentially expensive duplication:
let first = String::from("hello");
let second = first.clone(); // allocates a new buffer and copies the bytes
println!("{first} {second}");
clone() is the right tool when you genuinely need a second, independent owned value. It's the wrong tool as a reflex every time the borrow checker complains, because that usually hides a design question (who should own this?) behind an allocation.
Slices: borrowing part of a value¶
A slice is a reference to a contiguous part of something. The two you'll meet constantly:
| Owned type | Borrowed view | Notes |
|---|---|---|
String |
&str |
String literals ("hi") are &'static str. |
Vec<T> |
&[T] |
Arrays [T; N] also coerce to &[T]. |
PathBuf |
&Path |
Filesystem paths. |
OsString |
&OsStr |
Platform-native strings. |
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
fn main() {
let owned = String::from("hello world");
println!("{}", first_word(&owned)); // &String coerces to &str
println!("{}", first_word("literal")); // works with literals too
}
Guideline: function parameters should usually take the borrowed form (&str, &[T], &Path) rather than &String or &Vec<T>. It's strictly more flexible for callers and costs nothing.
Lifetimes: how long is a reference valid?¶
A lifetime is the region of code during which a reference is valid. The compiler tracks lifetimes for every reference; most of the time it infers them silently. You only write them when a function's signature is ambiguous about how its inputs and outputs relate.
The compiler refuses to let a reference outlive its data:
let r;
{
let x = 5;
r = &x; // error[E0597]: `x` does not live long enough
}
println!("{r}");
When you have to annotate¶
Consider a function that returns one of two string slices:
fn longer(a: &str, b: &str) -> &str { // error[E0106]: missing lifetime specifier
if a.len() >= b.len() { a } else { b }
}
The returned reference points into either a or b, and the compiler can't tell which from the signature alone. You tell it with a lifetime parameter:
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
Read 'a as: "the result is valid for as long as both inputs are valid." Lifetime annotations describe relationships; they never make anything live longer.
Elision: why you rarely write them¶
The compiler applies three elision rules so common signatures need no annotations:
- Each reference parameter gets its own lifetime.
- If there is exactly one input lifetime, it's assigned to all output references.
- If one of the parameters is
&selfor&mut self, its lifetime is assigned to all output references.
That's why fn first_word(s: &str) -> &str and fn name(&self) -> &str need nothing extra.
Structs that hold references¶
A struct that stores a reference must declare a lifetime, because the struct can't outlive what it borrows:
struct Parser<'src> {
input: &'src str,
pos: usize,
}
This is useful for short-lived, zero-copy views (parsers, iterators). For long-lived data, prefer owning fields (String, Vec<T>). Structs full of lifetime parameters are a frequent source of beginner pain, and owning the data is usually simpler and plenty fast.
'static¶
'static means "valid for the entire run of the program." String literals are &'static str. You'll also see the bound T: 'static, as in std::thread::spawn and tokio::spawn. That bound means "T contains no borrowed references that could expire," which is satisfied by any fully-owned type such as String or Vec<u8>. It does not mean the value lives forever.
When one owner isn't enough: smart pointers¶
Single ownership covers most code. When it genuinely doesn't (a graph, a cache shared between threads, application state shared by request handlers), the standard library provides types that implement other ownership policies:
| Type | Policy | Typical use |
|---|---|---|
Box<T> |
Single owner, value on the heap | Recursive types, large values, trait objects (Box<dyn Trait>). |
Rc<T> |
Shared ownership via reference counting, single thread | Trees/graphs where several nodes point at one value. |
Arc<T> |
Shared ownership, atomic reference count, thread-safe | Sharing config or state across threads/async tasks. |
RefCell<T> |
Borrow rules checked at runtime, single thread | Interior mutability behind an Rc. |
Mutex<T> / RwLock<T> |
Exclusive / reader-writer locking, thread-safe | Mutable state behind an Arc. |
Cow<'a, T> |
Borrowed until you need to modify, then owned | Functions that usually return input unchanged. |
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..4)
.map(|_| {
let counter = Arc::clone(&counter); // new owner, same value
thread::spawn(move || {
*counter.lock().unwrap() += 1;
})
})
.collect();
for h in handles {
h.join().unwrap();
}
println!("{}", *counter.lock().unwrap()); // 4
}
Rc<RefCell<T>> and Arc<Mutex<T>> are legitimate tools, but if they appear everywhere it's usually a sign the program's ownership structure needs rethinking. See Architecting Rust Projects for design alternatives.
Common compiler errors and what they mean¶
| Code | Message (abridged) | What usually happened | Usual fix |
|---|---|---|---|
| E0382 | use of moved / partially moved value | You moved a value (or a field of it), then used it again. | Borrow instead (&x, ref, match &x), reorder, or clone() if a second owner is truly needed. |
| E0499 | cannot borrow as mutable more than once | Two live &mut to the same value. |
Shorten one borrow's scope; split the struct; use indices. |
| E0502 | cannot borrow as mutable because it is also borrowed as immutable | A & is still live when you take &mut. |
Finish using the & first, or copy out what you need. |
| E0505 | cannot move out because it is borrowed | Moving a value while a reference to it is live. | End the borrow before the move. |
| E0507 | cannot move out of a shared reference | Taking ownership of something you only borrowed. | Borrow the inner value, clone(), or std::mem::take/Option::take. |
| E0597 | does not live long enough | A reference outlives its data. | Move the owner to an outer scope, or return owned data. |
| E0106 | missing lifetime specifier | A returned reference's source is ambiguous. | Add a lifetime parameter, or return an owned value. |
| E0596 | cannot borrow as mutable | The binding isn't declared mut. |
let mut x, or take &mut self. |
rustc --explain E0382 prints a full explanation with examples for any of these.
A practical checklist¶
When working with a value, ask:
- Who owns this? Exactly one place should be responsible for it.
- Does this code need ownership, or just access? Reading →
&T. Modifying →&mut T. Keeping or consuming →T. - Will the value be needed again after this line? If so, don't move it; borrow it.
- Will the reference outlive the data? If a reference needs to escape a function, return owned data instead.
- Is a clone a design decision or a band-aid? Clone deliberately, not reflexively.
Quick reference¶
| Concept | Plain-language meaning |
|---|---|
| Ownership | Exactly one part of the program is responsible for a value. |
| Drop | The value is cleaned up when its owner goes out of scope. |
| Move | Ownership transfers; the old binding can't be used. |
| Borrow | Temporary access via a reference; ownership stays put. |
&T |
Shared reference: read-only, many allowed. |
&mut T |
Exclusive reference: read-write, only one allowed. |
| NLL | Borrows end at the reference's last use, not the end of the block. |
Copy |
Implicit, cheap bitwise duplication; original stays usable. |
Clone |
Explicit, possibly expensive duplication. |
| Slice | A borrowed view into part of a collection (&str, &[T]). |
| Partial move | A field was moved out, so the whole value can't be used. |
| Lifetime | The region where a reference is valid; annotations describe relationships. |
Rc / Arc |
Shared ownership via reference counting. |
RefCell / Mutex |
Mutation through a shared handle, checked at runtime. |
Related¶
- Fixing "use of partially moved value": a worked example of E0382 in a
match. - Architecting Rust Projects: A Comprehensive Guide: how ownership shapes larger designs.
- The Rust Programming Language, chapter 4: the official treatment of ownership.
- Rust by Example: Scoping rules: runnable snippets for RAII, ownership, borrowing, and lifetimes.