SOP: Ownership & Borrowing ​
Updated Mar 2026Overview ​
Rust's ownership system is the foundation of memory safety without garbage collection. This SOP covers the rules, patterns, and how to satisfy the borrow checker.
The Three Rules ​
Move vs Copy vs Clone ​
Move Semantics ​
rust
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED to s2
// println!("{s1}"); // ERROR: s1 no longer valid
let x: i32 = 5;
let y = x; // x is COPIED (i32 implements Copy)
println!("{x} {y}"); // Both valid
}Clone for Explicit Deep Copy ​
rust
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // Explicit deep copy
println!("{s1} {s2}"); // Both valid
}Borrowing Rules ​
Shared References ​
rust
fn calculate_length(s: &String) -> usize {
s.len() // Can read but not modify
}
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // Borrow s
println!("{s} has length {len}"); // s still valid
}Mutable References ​
rust
fn append_world(s: &mut String) {
s.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
append_world(&mut s);
println!("{s}"); // "hello, world"
}The Exclusivity Rule ​
rust
fn main() {
let mut s = String::from("hello");
let r1 = &s; // OK — first shared borrow
let r2 = &s; // OK — second shared borrow
println!("{r1} {r2}");
// r1 and r2 no longer used after this point (NLL)
let r3 = &mut s; // OK — no shared borrows alive
println!("{r3}");
}Common Patterns ​
Pattern: Returning References with Lifetimes ​
rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("long string");
let result;
{
let s2 = String::from("xyz");
result = longest(s1.as_str(), s2.as_str());
println!("{result}"); // Must use while s2 is alive
}
// result can't be used here — s2 is dropped
}Pattern: Struct Borrowing ​
rust
struct Excerpt<'a> {
text: &'a str, // Struct holds a reference — needs lifetime
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 {
3 // No reference in return — no lifetime needed
}
fn announce(&self, announcement: &str) -> &str {
// Lifetime elision: output gets &self's lifetime
self.text
}
}Pattern: Borrowing in Loops ​
rust
fn main() {
let mut data = vec![1, 2, 3, 4, 5];
// Immutable iteration — borrows data
for item in &data {
println!("{item}");
}
// Mutable iteration — mutably borrows data
for item in &mut data {
*item *= 2;
}
// Consuming iteration — moves data
for item in data {
println!("{item}");
}
// data is no longer valid here
}Troubleshooting the Borrow Checker ​
| Error | Cause | Fix |
|---|---|---|
| "cannot move out of borrowed content" | Trying to take ownership from a reference | Clone, or restructure to use references |
| "cannot borrow as mutable more than once" | Two &mut to same data | Scope the borrows so they don't overlap |
| "cannot borrow as mutable because also borrowed as immutable" | & and &mut coexist | Finish using & before taking &mut |
| "does not live long enough" | Reference outlives data | Extend data's lifetime or clone |
| "missing lifetime specifier" | Compiler can't infer lifetimes | Add explicit lifetime annotations |
Decision Checklist ​
- [ ] Determine if you need to transfer ownership or borrow
- [ ] If borrowing, determine if you need shared (&T) or exclusive (&mut T) access
- [ ] Check that no &mut overlaps with any other borrow of the same data
- [ ] For returned references, verify lifetime annotations are correct
- [ ] For struct fields that are references, add lifetime parameters
- [ ] Prefer owned types (String, Vec) over references when lifetime complexity is high