| name | rust-ownership |
| description | | Use when this capability is needed. |
Quick Navigation
Rust Ownership & Lifetimes
The borrow checker is a design tool, not an obstacle. When it pushes back, rethink data ownership.
Core Mental Model
Ownership = exclusive control over data lifetime.
Every value has one owner. When the owner is dropped, the value is freed. No GC, no double-free.
Ask before writing code:
- Who owns this data?
- How long does it need to live?
- Who needs to read it vs modify it?
Ownership Errors → Design Questions
| Error | Reflexive fix (wrong) | Right question |
|---|
| E0382 use of moved value | .clone() it | Who should own this? |
| E0597 doesn't live long enough | Add 'static | Is the scope boundary wrong? |
| E0506 cannot assign to borrowed | Re-borrow mutably | Should mutation happen here? |
| E0507 move out of borrowed | .clone() the field | Should the caller give ownership? |
| E0515 return reference to local | Return owned type | Should caller pass in a buffer? |
Borrowing Rules (Compile-time)
At any point in code: either
- ONE mutable reference (&mut T), or
- ANY NUMBER of immutable references (&T)
... but never both at the same time.
All references must be valid (no dangling pointers).
let mut data = vec![1, 2, 3];
let r1 = &data;
let r2 = &data;
println!("{:?} {:?}", r1, r2);
data.push(4);
Copy vs Move vs Clone
let x: i32 = 5;
let y = x;
println!("{x}");
let s = String::from("hello");
let t = s;
let s = String::from("hello");
let t = s.clone();
println!("{s} {t}");
Rule of thumb: If Copy doesn't make sense semantically (e.g., a file handle), use Clone only when you genuinely need two independent values.
Borrowing vs Cloning
fn print_name(name: String) { println!("{name}"); }
print_name(user.name.clone());
fn print_name(name: &str) { println!("{name}"); }
print_name(&user.name);
fn print_name(name: impl AsRef<str>) { println!("{}", name.as_ref()); }
print_name("literal");
print_name(&user.name);
print_name(user.name);
Lifetimes
Lifetimes tell the compiler how long references are valid. They're inferred in most cases (lifetime elision), but must be explicit when:
- Function returns a reference derived from multiple input references
- A struct holds a reference
fn first(s: &str) -> &str {
&s[..1]
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
struct Excerpt<'a> {
text: &'a str,
}
Common Lifetime Patterns
fn get_greeting() -> &'static str {
"Hello!"
}
fn build_greeting(name: &str) -> String {
format!("Hello, {name}!")
}
impl Cache {
fn get(&self, key: &str) -> Option<&str> {
self.map.get(key).map(String::as_str)
}
}
Cow: Flexible Ownership
Use Cow<'_, T> when a function sometimes needs to allocate and sometimes doesn't:
use std::borrow::Cow;
fn normalize_username(name: &str) -> Cow<'_, str> {
if name.chars().all(|c| c.is_lowercase()) {
Cow::Borrowed(name)
} else {
Cow::Owned(name.to_lowercase())
}
}
let n = normalize_username("Alice");
println!("{n}");
Interior Mutability
When you need mutation through a shared reference:
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
let data = RefCell::new(vec![1, 2, 3]);
data.borrow_mut().push(4);
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let data2 = Arc::clone(&data);
std::thread::spawn(move || {
data2.lock().unwrap().push(4);
});
use std::sync::RwLock;
let data = Arc::new(RwLock::new(HashMap::new()));
let r = data.read().unwrap();
let mut w = data.write().();
Self-Referential Structs
Avoid self-referential structs — they're a borrow checker nightmare. Instead:
struct SelfRef {
data: String,
}
struct Parser {
input: String,
pos: usize,
}
use std::pin::Pin;
struct Parser<'a> {
input: &'a str,
pos: usize,
}
Quick Fixes for Common Errors
let name = String::from("Alice");
let greeting = move || println!("{name}");
let name = String::from("Alice");
let name2 = name.clone();
let greeting = move || println!("{name}");
println!("{name2}");
let mut v = vec![1, 2, 3];
let first = v[0];
v.push(4);
println!("{first}");
fn bad() & {
= ::();
&s
}
() {
::()
}
Special Patterns
1. Conditional Ownership with Cow
Use std::borrow::Cow (Clone-on-Write) when a function can accept either a borrowed or owned representation, only allocating when write/mutation is required.
use std::borrow::Cow;
fn sanitize_username<'a>(username: &'a str) -> Cow<'a, str> {
if username.chars().all(|c| c.is_lowercase()) {
Cow::Borrowed(username)
} else {
Cow::Owned(username.to_lowercase())
}
}
2. Interior Mutability: RefCell vs Mutex
Use interior mutability structures to allow mutating data through immutable (&T) references.
- Single-threaded: Use
RefCell<T> (checked at runtime; panics on dynamic borrow conflicts).
- Multi-threaded: Use
Mutex<T> (blocks threads dynamically) or RwLock<T> (if reads dominate writes).
- Prefer cell-types (
Cell<T>) for simple copyable primitives (i32, bool) to bypass borrow checker checks entirely.
Checklist: Before Cloning
References
Source: adxptived/Rust-Skills — distributed by TomeVault.