Claude-initial-setup ownership-borrowing
git clone https://github.com/VersoXBT/claude-initial-setup
T=$(mktemp -d) && git clone --depth=1 https://github.com/VersoXBT/claude-initial-setup "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/rust/ownership-borrowing" ~/.claude/skills/versoxbt-claude-initial-setup-ownership-borrowing && rm -rf "$T"
skills/rust/ownership-borrowing/SKILL.mdRust Ownership and Borrowing
Understand and apply Rust's ownership system to write memory-safe code without a garbage collector. Master borrowing rules, lifetimes, and smart pointers.
When to Use
- Encountering borrow checker errors ("cannot borrow as mutable")
- Deciding between references, cloning, and moving
- Adding lifetime annotations to structs or functions
- Choosing between Box, Rc, Arc for heap allocation and sharing
- Understanding when to derive Clone vs Copy
Core Patterns
Pattern 1: Ownership Rules
Three rules govern all Rust memory:
- Each value has exactly one owner.
- When the owner goes out of scope, the value is dropped.
- Ownership can be transferred (moved) but not duplicated (unless Copy).
fn main() { let name = String::from("Alice"); // name owns the String let greeting = greet(name); // ownership moves to greet // println!("{name}"); // ERROR: name was moved println!("{greeting}"); } fn greet(name: String) -> String { // takes ownership format!("Hello, {name}!") // returns a new owned String }
Pattern 2: Borrowing -- Shared and Mutable References
Borrow data without taking ownership. Two rules:
- Any number of shared references (
) OR exactly one mutable reference (&T
).&mut T - References must always be valid (no dangling pointers).
fn analyze(data: &[i32]) -> (i32, i32) { // Shared borrow: can read, cannot modify let sum: i32 = data.iter().sum(); let count = data.len() as i32; (sum, count) } fn normalize(data: &mut Vec<f64>) { // Mutable borrow: can read and modify let max = data.iter().cloned().fold(f64::NEG_INFINITY, f64::max); if max != 0.0 { for val in data.iter_mut() { *val /= max; } } } fn main() { let mut values = vec![1.0, 2.0, 3.0]; normalize(&mut values); // mutable borrow let (sum, _) = analyze(&[1, 2, 3]); // shared borrow println!("{sum}"); }
Pattern 3: Lifetimes
Lifetimes tell the compiler how long references are valid. Most are inferred; annotate when the compiler cannot determine the relationship.
// The returned reference lives as long as the shortest input lifetime fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b } } // Lifetime in structs: the struct cannot outlive the referenced data struct Excerpt<'a> { text: &'a str, } impl<'a> Excerpt<'a> { fn first_word(&self) -> &'a str { self.text.split_whitespace().next().unwrap_or("") } } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let excerpt = Excerpt { text: novel.split('.').next().unwrap(), }; println!("{}", excerpt.first_word()); }
Pattern 4: Smart Pointers
Use smart pointers when ownership rules need more flexibility.
// Box<T>: heap allocation, single owner fn build_tree() -> Box<Node> { Box::new(Node { value: 1, left: Some(Box::new(Node { value: 2, left: None, right: None })), right: Some(Box::new(Node { value: 3, left: None, right: None })), }) } // Rc<T>: shared ownership, single-threaded (reference counted) use std::rc::Rc; fn shared_config() { let config = Rc::new(AppConfig::default()); let service_a = Service::new(Rc::clone(&config)); let service_b = Service::new(Rc::clone(&config)); // Both services share the same config; dropped when last Rc is dropped } // Arc<T>: shared ownership, thread-safe (atomic reference counted) use std::sync::Arc; fn concurrent_cache() { let cache = Arc::new(Mutex::new(HashMap::new())); let handles: Vec<_> = (0..4) .map(|i| { let cache = Arc::clone(&cache); std::thread::spawn(move || { cache.lock().unwrap().insert(i, i * 10); }) }) .collect(); for h in handles { h.join().unwrap(); } }
Pattern 5: Clone vs Copy
Copy: bitwise copy, implicit, for small stack types (integers, bools, tuples of Copy types).
Clone: explicit .clone(), for types that need deep duplication.
// Copy: automatically duplicated on assignment let x: i32 = 42; let y = x; // x is copied, both x and y are valid println!("{x} {y}"); // Clone: explicit duplication let a = String::from("hello"); let b = a.clone(); // deep copy -- both a and b are valid println!("{a} {b}"); // Without clone, a would be moved: let c = String::from("world"); let d = c; // c is MOVED to d // println!("{c}"); // ERROR: c was moved // Derive both when your struct contains only Copy types #[derive(Debug, Clone, Copy)] struct Point { x: f64, y: f64, }
Pattern 6: Common Borrow Checker Patterns
Solutions to frequent borrow checker issues.
// Problem: cannot borrow as mutable because also borrowed as immutable // Solution: limit the scope of the immutable borrow fn update_map(map: &mut HashMap<String, Vec<i32>>, key: &str) { // Use entry API to avoid double-borrow map.entry(key.to_string()) .or_insert_with(Vec::new) .push(42); } // Problem: returning a reference to a local variable // Solution: return owned data fn create_greeting(name: &str) -> String { // Return owned String, not &str format!("Hello, {name}!") } // Problem: self-referential struct // Solution: use indices instead of references, or use Pin struct Document { content: String, // Instead of &str references into content, store byte offsets highlights: Vec<(usize, usize)>, } impl Document { fn highlighted_text(&self, idx: usize) -> &str { let (start, end) = self.highlights[idx]; &self.content[start..end] } }
Anti-Patterns
-
Cloning to silence the borrow checker --
everywhere is a sign of misunderstood ownership. Restructure code to use borrows instead..clone() -
Unnecessary
-- Only use Arc when data is shared across threads. Single-threaded code should use Rc or plain ownership.Arc<Mutex<T>> -
Lifetime annotations on everything -- Let the compiler infer lifetimes. Only annotate when the compiler asks or when the relationship is ambiguous.
-
Returning references from functions that create data -- Functions that allocate must return owned types. References can only point to data that outlives them.
Quick Reference
| Type | Ownership | Thread-safe | Heap | Use Case |
|---|---|---|---|---|
| Sole owner | N/A | Stack* | Default |
| Shared borrow | Yes (if T: Sync) | No | Read access |
| Exclusive borrow | No | No | Write access |
| Sole owner | N/A | Yes | Large/recursive types |
| Shared | No | Yes | Single-thread sharing |
| Shared | Yes | Yes | Multi-thread sharing |
Move vs Copy: types implementing
Copy are duplicated on assignment; all others are moved.