Ownership and Borrowing
Lookup sheet for stage 1. The question it exists to answer: why was this rejected, and what is the fix that is not a clone?
The rules
- Every value has exactly one owner.
- When the owner goes out of scope, the value is dropped.
- At any point, a value has either any number of shared borrows (
&T) or exactly one mutable borrow (&mut T), never both. - Every borrow must be valid for as long as it is used.
A borrow ends at its last use, not at the end of the scope.
Move or copy
| Assignment or argument passing | Result |
|---|---|
type is Copy | value duplicated, both names usable |
type is not Copy | ownership moved, old name unusable |
&T passed | copied, since &T is Copy |
&mut T passed to a function | implicitly reborrowed, original usable after the call |
&mut T stored in a struct | genuinely moved; use &mut *r to reborrow explicitly |
Copy or not
Copy | Not Copy |
|---|---|
i8 to i128, u8 to u128, usize, isize | String, Vec<T>, Box<T>, HashMap |
f32, f64, bool, char | &mut T |
&T | anything implementing Drop |
tuples and arrays of Copy types | any struct or enum without #[derive(Copy)] |
Option<T>, Result<T, E> with Copy contents | closures capturing by move |
Copy requires Clone. A type with a Drop implementation can never be Copy.
Owned against borrowed
| Owned | Borrowed | Take this in a signature |
|---|---|---|
String | &str | &str |
Vec<T> | &[T] | &[T] |
[T; N] | &[T] | &[T] |
PathBuf | &Path | &Path |
Box<T> | &T | &T |
Rule of thumb: take the borrowed form, return the owned form. Deref coercion means a caller holding the owned type passes it with & and keeps it.
Error codes
| Code | Headline | Honest fix |
|---|---|---|
E0382 | use of moved value | change the signature to borrow, or return the value back |
E0499 | two mutable borrows | end one sooner, or split the data |
E0502 | mutable while shared | move the last use of the shared borrow earlier, or copy the value out |
E0505 | move out while borrowed | end the borrow before the move |
E0596 | cannot borrow as mutable | add mut to the binding |
E0106 | missing lifetime specifier | stage 4: say how long the reference is valid |
E0515 | returns a reference to local data | return an owned value instead |
rustc --explain E0502 prints the page offline.
Reading the message
Three spans matter, and the third is the useful one:
- where the first borrow began,
- where the conflicting borrow or move is,
- where the first borrow is still used, which is what keeps it alive.
If span 3 can move above span 2, the error disappears with no restructuring.
The bar is the borrow's live range, and only its end moved. No data was cloned, no signature changed, and no struct was split: the last use went above the mutation, which is the first of the four honest fixes below.
The four honest fixes
- End the borrow sooner. Move the last use up, or wrap it in a block.
- Copy the value out.
let x = v[0];for aCopyelement retains no borrow. - Borrow in the signature.
&strinstead ofString,&[T]instead ofVec<T>. - Restructure. Split a struct, or compute before mutating instead of interleaving.
The four workarounds
| Workaround | Legitimate when | Cost otherwise |
|---|---|---|
.clone() | two independent values are wanted | allocation, and values that silently diverge |
Rc<RefCell<T>> | shared ownership is the real design | compile-time check becomes a run-time panic; not thread-safe |
| indices instead of references | traversing while mutating | no bounds guarantee; stale indices |
unsafe | not at this stage | undefined behaviour, and the rules still apply |
Disjoint fields
The compiler borrows fields separately:
let name = &self.config.name; // borrows self.config
self.cache.insert(name); // mutates self.cache: fine
A method call taking &self borrows all of self, which loses that precision:
let name = self.name(); // borrows all of self
self.cache.insert(name); // E0502
Fixes: read the field directly, return an owned value from the method, or split the struct.
Drop order
- Within a scope: reverse declaration order.
- A struct: its own
Drop::dropfirst, then its fields in declaration order. - Early release:
drop(value), an ordinary function that takes ownership.