rust-ownership
Skill1 starsChanged 6 months agoMITView on GitHub
Rust ownership, borrowing, and lifetimes — the core memory model. ALWAYS use this skill when: - User hits E0382 (use of moved value), E0505 (cannot move out of), E0506 (cannot assign to borrowed), E0507 (cannot move out of borrowed), E0515 (returning reference to local), E0597 (borrowed value does not live long enough) - User asks "why can't I use X after passing it to a function?" - User encounters "borrowed value does not live long enough" - User works with self-referential structs, Rc/Arc, or complex lifetime annotations - User asks about 'static, 'a, lifetime elision, or NLL (non-lexical lifetimes) - User clones excessively to appease the borrow checker - User asks about Copy vs Clone, move semantics, or references Trigger for ANY ownership/borrow/lifetime error or question.
2 other projects use a version of this.
Adopted by
Most recently taken up by
- dawidpereira/rust-skillstheir own version, first seen Mar 22, 2026
- huiali/rust-skillstheir own version, first seen Feb 9, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- huiali/rust-skills★ 47same name
- dawidpereira/rust-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository24 other files it gives its agents.
- rust-api-designSkill
- rust-asyncSkill
- rust-axumSkill
- rust-cliSkill
- rust-cloud-nativeSkill
- rust-concurrencySkill
- rust-databaseSkill
- rust-dioxusSkill
- rust-ecosystemSkill
- rust-embeddedSkill
- rust-error-handlingSkill
- rust-fintechSkill
- rust-masterySkill
- rust-memory-optimizationSkill
- rust-mlSkill
- rust-networkingSkill
- rust-observabilitySkill
- rust-design-patternsSkill
- rust-performanceSkill
- rust-securitySkill
- rust-testingSkill
- rust-type-systemSkill
- rust-unsafeSkill
- rust-wasmSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 3 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 29
Every value has one owner.
- Very specific: line 87
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.
- Very specific: line 155
Use Cow<'_, T> when a function sometimes needs to allocate and sometimes doesn't:
- Distinctive: line 175
When you need mutation through a shared reference:
- Distinctive: line 201
Avoid self-referential structs — they're a borrow checker nightmare.
- Very specific: line 263
Use std::borrow::Cow (Clone-on-Write) when a function can accept either a borrowed or owned representation, only allocating when …
- Very specific: line 277
Use interior mutability structures to allow mutating data through immutable (&T) references.
- Very specific: line 278
Single-threaded: Use RefCell<T> (checked at runtime.
- Very specific: line 279
Multi-threaded: Use Mutex<T> (blocks threads dynamically) or RwLock<T> (if reads dominate writes).
- Very specific: line 280
Prefer cell-types (Cell<T>) for simple copyable primitives (i32, bool) to bypass borrow checker checks entirely.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Quick Navigation
- references/lifetimes.md
- references/borrow_checker.md
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];
// Multiple immutable borrows — OK
let r1 = &data;
let r2 = &data;
println!("{:?} {:?}", r1, r2); // r1 and r2 last used here
// Mutable borrow after immutable borrows END (NLL)
data.push(4); // OK: r1 and r2 no longer in useCopy vs Move vs Clone
// Copy types (stack-only, bitwise copy): i32, f64, bool, char, &T, [T; N] if T: Copy
let x: i32 = 5;
let y = x; // Copied, x still valid
println!("{x}"); // Fine
// Move types (heap data): String, Vec<T>, Box<T>
let s = String::from("hello");
let t = s; // MOVED — s is no longer valid
// println!("{s}"); // E0382!
// Clone: explicit deep copy
let s = String::from("hello");
let t = s.clone(); // Explicit copy, both valid
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
// Bad: Cloning just to pass to a read-only function
fn print_name(name: String) { println!("{name}"); }
print_name(user.name.clone()); // Wasteful
// Good: Borrow instead
fn print_name(name: &str) { println!("{name}"); }
print_name(&user.name); // Zero cost
// Good: Accept impl AsRef<str> for maximum flexibility
fn print_name(name: impl AsRef<str>) { println!("{}", name.as_ref()); }
print_name("literal"); // &str
print_name(&user.name); // &String → auto-deref to &str
print_name(user.name); // Owned StringLifetimes
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
// Elision works: compiler knows output comes from input
fn first(s: &str) -> &str {
&s[..1] // lifetime of return = lifetime of s
}
// Must annotate: which input does the output borrow from?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 'a = the shorter of x's and y's lifetime
// Struct holding a reference
struct Excerpt<'a> {
text: &'a str, // Can't outlive the string it came from
}Common Lifetime Patterns
// 'static: lives for the entire program (string literals, leaked data)
fn get_greeting() -> &'static str {
"Hello!" // String literals are 'static
}
// Returning owned value — avoids lifetime complexity
fn build_greeting(name: &str) -> String {
format!("Hello, {name}!")
}
// Output lifetime from self (method pattern)
impl Cache {
fn get(&self, key: &str) -> Option<&str> {
// return lifetime = self's lifetime (elision rule 3)
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) // No allocation needed
} else {
Cow::Owned(name.to_lowercase()) // Allocates only when necessary
}
}
// Caller doesn't care which variant they got
let n = normalize_username("Alice");
println!("{n}"); // Works for both Borrowed and OwnedInterior Mutability
When you need mutation through a shared reference:
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
// Single-threaded: RefCell (runtime borrow checking)
let data = RefCell::new(vec![1, 2, 3]);
data.borrow_mut().push(4); // Panics if already mutably borrowed
// Multi-threaded: Arc<Mutex<T>>
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let data2 = Arc::clone(&data);
std::thread::spawn(move || {
data2.lock().unwrap().push(4);
});
// Read-heavy: Arc<RwLock<T>>
use std::sync::RwLock;
let data = Arc::new(RwLock::new(HashMap::new()));
let r = data.read().unwrap(); // Many readers at once
let mut w = data.write().unwrap(); // Exclusive writerSelf-Referential Structs
Avoid self-referential structs — they're a borrow checker nightmare. Instead:
// Bad: struct referencing its own field
struct SelfRef {
data: String,
// ptr: &'??? str, // What lifetime? It references `data`!
}
// Good option 1: Store indices instead of references
struct Parser {
input: String,
pos: usize, // Position into input
}
// Good option 2: Pin + unsafe (for truly needed cases)
use std::pin::Pin;
// Use `pin-project` crate for safe pinned projections
// Good option 3: Separate lifetime (caller owns the data)
struct Parser<'a> {
input: &'a str, // Caller's data, parser borrows it
pos: usize,
}Quick Fixes for Common Errors
// E0382: Use of moved value
// Problem: moved into a closure, then used again
let name = String::from("Alice");
let greeting = move || println!("{name}");
// println!("{name}"); // ERROR
// Fix: clone before the move
let name = String::from("Alice");
let name2 = name.clone();
let greeting = move || println!("{name}");
println!("{name2}"); // OK
// E0502: Can't borrow mutably while immutably borrowed
let mut v = vec![1, 2, 3];
let first = v[0]; // Copy the value (i32 is Copy)
v.push(4); // OK: no borrow active
println!("{first}");
// E0597: Borrowed value doesn't live long enough
// Problem: returning reference to local
fn bad() -> &str {
let s = String::from("hello");
&s // s dropped here, reference dangles!
}
// Fix: return owned
fn good() -> String {
String::from("hello")
}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) // Zero allocation
} else {
Cow::Owned(username.to_lowercase()) // Allocates only when uppercase exists
}
}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) orRwLock<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
- Does the callee actually need ownership, or just to read?
- Can I change the function signature to accept
&Tor&str? - Could
Coweliminate the clone in the common path? - Am I cloning in a loop? Consider restructuring ownership.
- Is this a design smell — does one value need to be in two places?
References
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

