Ownership and borrowing
Goal
No allocation that a borrow would cover. Shared mutability only when the type system requires it.
Inputs / outputs
- In: a function or type that owns or shares data
- Out: API that takes the least ownership it needs
Reads / writes
- Read: the function and its callers
- Write: none unless a playbook opened the task
Approval
None to drop clones. Stop for approval before adding Arc, Rc, Mutex, RwLock, or RefCell.
RULES — no exceptions
- Prefer
&T/&mut Tover.clone() - Accept
&[T]not&Vec<T>;&strnot&String Cow<'_, T>only when you sometimes ownArc<T>across threads;Rc<T>single-thread onlyRefCell/Mutex/RwLocklast; document whyCopyfor tiny, obviously copyable types; otherwise explicitClone- Move large values;
Boxif the move itself is the cost - Elide lifetimes until the compiler asks
Example
// ❌
fn count_words(text: &String) -> usize {
text.clone().split_whitespace().count()
}
// ✅
fn count_words(text: &str) -> usize {
text.split_whitespace().count()
}
Clone is justified when storing, sending 'static to a thread, or the type is Copy.
Steps
- Grep the touched fn for
.clone( - If the clone is not stored or sent, borrow
- Widen params to slices/str
Validation
cargo test on the crate (or cargo test <test_name> -- --exact if a test is already red). Diff should drop clones or justify each remainder in one comment.
Pitfalls
| ❌ | ✅ |
|---|---|
data.clone() to call a reader |
pass &data |
Mutex for a single-thread cache |
RefCell or, better, owned local |
| Lifetime soup | elide; name only 'src / 'a when needed |
Integration
Predecessor: rust-essentials. Successor: type-driven-design. Do not hold a lock across .await.