Rust Ownership & Borrowing: Lifetimes Thoroughly Explained
Rust guarantees memory safety without a GC, powered by ownership and borrow checker. Validates legality at compile time with zero runtime cost but steepest learning curve.
Three Iron Ownership Rules
(1) Every value has exactly one owner. (2) When owner leaves scope value is dropped. (3) A value can have either one mutable reference OR any immutable references - never both at once. Most beginner lifetime errors violate #3.
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct User { id: u32, username: String, profile: Option<Box<User>> }
struct UserCache<'a> {
users: HashMap<u32, User>,
last_accessed: Option<&'a User>,
}
impl<'a> UserCache<'a> {
fn new() -> Self { Self { users: HashMap::new(), last_accessed: None } }
fn insert(&mut self, user: User) { self.users.insert(user.id, user); }
fn get(&'a mut self, id: u32) -> Option<&'a User> {
if let Some(u) = self.users.get(&id) {
self.last_accessed = Some(u);
self.last_accessed
} else { None }
}
}
fn longest_str<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() >= y.len() { x } else { y } }
struct Parser<'s, 'b> where 's: 'b { source: &'s str, buffer: &'b mut [u8] }
impl<'s, 'b> Parser<'s, 'b> where 's: 'b {
fn new(source: &'s str, buffer: &'b mut [u8]) -> Self { Self { source, buffer } }
}
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1={s1}, s2={s2}");
let a = String::from("short"); let b = String::from("longest string wins");
println!("longest: {}", longest_str(&a, &b));
let mut cache = UserCache::new();
cache.insert(User { id: 1, username: "rosetta".into(), profile: None });
let u = cache.get(1);
println!("Got user: {:?}", u.map(|x| &x.username));
let mut buf = vec![0u8; 1024];
let p = Parser::new("input", buf.as_mut_slice());
println!("Parser source len: {}", p.source.len());
}
Lifetime Annotations and Bounds
When functions/structs contain references, Rust needs explicit labels ('a, 'b) expressing validity relationships. Bound 's: 'b means lifetime 's outlives 'b - common in self-referential structs.
| Common Error | Root Cause | Fix Pattern |
|---|---|---|
| use of moved value | Ownership transferred already | .clone() or borrow with & |
| cannot borrow X as mutable + immutable | Borrow rule violated | Narrow scopes / split blocks |
| missing lifetime specifier | Compiler can't infer relation | Add 'a tying inputs/outputs |
| lifetime may not live long enough | Referenced value dropped earlier | Extend scope / owning types |
Best Practices
First versions use owning types everywhere (String/Vec/Box). After passing, convert parameters to references. Don't chase lifetime annotations prematurely - let compiler errors drive incremental fixes.