agentleFS
adxptivedadxptived/Rust-Skills/skills/rust-ownership/SKILL.md

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

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
  1. Distinctive:

    Every value has one owner.

    line 29
  2. Very specific:

    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.

    line 87
  3. Very specific:

    Use Cow<'_, T> when a function sometimes needs to allocate and sometimes doesn't:

    line 155
  4. Distinctive:

    When you need mutation through a shared reference:

    line 175
  5. Distinctive:

    Avoid self-referential structs — they're a borrow checker nightmare.

    line 201
  6. Very specific:

    Use std::borrow::Cow (Clone-on-Write) when a function can accept either a borrowed or owned representation, only allocating when …

    line 263
  7. Very specific:

    Use interior mutability structures to allow mutating data through immutable (&T) references.

    line 277
  8. Very specific:

    Single-threaded: Use RefCell<T> (checked at runtime.

    line 278
  9. Very specific:

    Multi-threaded: Use Mutex<T> (blocks threads dynamically) or RwLock<T> (if reads dominate writes).

    line 279
  10. Very specific:

    Prefer cell-types (Cell<T>) for simple copyable primitives (i32, bool) to bypass borrow checker checks entirely.

    line 280

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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

ErrorReflexive fix (wrong)Right question
E0382 use of moved value.clone() itWho should own this?
E0597 doesn't live long enoughAdd 'staticIs the scope boundary wrong?
E0506 cannot assign to borrowedRe-borrow mutablyShould mutation happen here?
E0507 move out of borrowed.clone() the fieldShould the caller give ownership?
E0515 return reference to localReturn owned typeShould 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 use

Copy 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 String

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
// 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 Owned

Interior 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 writer

Self-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) 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

  • Does the callee actually need ownership, or just to read?
  • Can I change the function signature to accept &T or &str?
  • Could Cow eliminate 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

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.