smithery/imehr

rust-ownership

Ownership, borrowing, and lifetime patterns

Installation

$ npx skills add smithery/imehr --skill rust-ownership

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/imehr · top by installs.

npx skills add smithery/imehr

Browse all from smithery/imehr

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.0.0

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,620 B
  • docs SUMMARY.md 65 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Rust Ownership Guidelines

Overview

This skill provides patterns for working with Rust's ownership system, borrowing rules, and lifetimes. Understanding these concepts is essential for writing safe, efficient Rust code.

Quick Reference

Concept Rule Example
Ownership One owner at a time let s2 = s1; (s1 moved)
Borrowing Multiple &T OR one &mut T &data or &mut data
Lifetimes References must not outlive data 'a, 'static
Clone Explicit deep copy data.clone()
Copy Implicit bit copy (simple types) i32, bool, char

Ownership Rules

  1. Each value has exactly one owner
  2. When owner goes out of scope, value is dropped
  3. Ownership can be transferred (moved) or borrowed

Core Patterns

Pattern 1: Move vs Clone vs Copy

// ✅ CORRECT: Understanding moves
fn main() {
    // Heap data - moves by default
    let s1 = String::from("hello");
    let s2 = s1;  // s1 is MOVED to s2
    // println!("{}", s1);  // ERROR: s1 no longer valid

    // Explicit clone when you need both
    let s3 = String::from("hello");
    let s4 = s3.clone();  // Deep copy
    println!("{} {}", s3, s4);  // Both valid

    // Stack data with Copy trait - copies automatically
    let x = 5;
    let y = x;  // x is COPIED (i32 implements Copy)
    println!("{} {}", x, y);  // Both valid
}

// Types that implement Copy (stack-only, fixed size):
// - All integers (i32, u64, etc.)
// - bool, char
// - Tuples of Copy types: (i32, bool)
// - Arrays of Copy types: [i32; 5]
// - References: &T (but not &mut T)

Pattern 2: Borrowing Rules

// ✅ CORRECT: Multiple immutable borrows
fn process_data(data: &Vec<i32>) {
    let sum: i32 = data.iter().sum();
    let len = data.len();
    println!("Sum: {}, Len: {}", sum, len);
}

fn main() {
    let data = vec![1, 2, 3, 4, 5];

    // Multiple immutable borrows - OK
    let r1 = &data;
    let r2 = &data;
    println!("{:?} {:?}", r1, r2);

    process_data(&data);  // Another immutable borrow
}

// ✅ CORRECT: Exclusive mutable borrow
fn add_item(data: &mut Vec<i32>, item: i32) {
    data.push(item);
}

fn main() {
    let mut data = vec![1, 2, 3];

    // Only ONE mutable borrow at a time
    let r = &mut data;
    r.push(4);
    // let r2 = &mut data;  // ERROR: cannot borrow twice

    add_item(&mut data, 5);  // OK - previous borrow ended
}

// ❌ WRONG: Mixing mutable and immutable borrows
fn bad_example() {
    let mut data = vec![1, 2, 3];
    let r1 = &data;        // Immutable borrow
    let r2 = &mut data;    // ERROR: cannot borrow as mutable
    println!("{:?}", r1);
}

// ✅ CORRECT: Borrows don't overlap
fn good_example() {
    let mut data = vec![1, 2, 3];
    let r1 = &data;
    println!("{:?}", r1);  // r1's last use

    let r2 = &mut data;    // OK - r1 no longer used (NLL)
    r2.push(4);
}

Pattern 3: Lifetimes in Functions

// ✅ CORRECT: Explicit lifetime annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Usage
fn main() {
    let s1 = String::from("long string");
    let result;
    {
        let s2 = String::from("xyz");
        result = longest(&s1, &s2);
        println!("Longest: {}", result);  // OK - s2 still valid
    }
    // println!("{}", result);  // Would be ERROR if result referenced s2
}

// ✅ CORRECT: Lifetime elision (compiler infers)
// These are equivalent:
fn first_word(s: &str) -> &str { ... }           // Elided
fn first_word<'a>(s: &'a str) -> &'a str { ... } // Explicit

// Elision rules:
// 1. Each input reference gets its own lifetime
// 2. If one input lifetime, output gets that lifetime
// 3. If &self or &mut self, output gets self's lifetime

Pattern 4: Lifetimes in Structs

// ✅ CORRECT: Struct holding a reference
struct Excerpt<'a> {
    text: &'a str,
}

impl<'a> Excerpt<'a> {
    fn new(text: &'a str) -> Self {
        Excerpt { text }
    }

    // Methods can use the struct's lifetime
    fn get_text(&self) -> &str {
        self.text
    }

    // Or introduce new lifetimes
    fn compare<'b>(&self, other: &'b str) -> bool {
        self.text == other
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let excerpt = Excerpt::new(first_sentence);
    println!("Excerpt: {}", excerpt.get_text());
}

// ❌ WRONG: Struct outliving its reference
fn bad_excerpt() -> Excerpt<'static> {
    let s = String::from("temporary");
    Excerpt { text: &s }  // ERROR: s doesn't live long enough
}

Pattern 5: Ownership in Structs

// ✅ CORRECT: Struct owns its data
struct User {
    name: String,      // Owned String
    email: String,     // Owned String
    age: u32,          // Copy type
}

impl User {
    fn new(name: String, email: String, age: u32) -> Self {
        User { name, email, age }
    }

    // Take ownership for modification
    fn with_email(mut self, email: String) -> Self {
        self.email = email;
        self
    }

    // Borrow for read-only access
    fn name(&self) -> &str {
        &self.name
    }

    // Mutable borrow for modification
    fn set_age(&mut self, age: u32) {
        self.age = age;
    }
}

// Builder pattern with ownership
let user = User::new("Alice".into(), "[email protected]".into(), 30)
    .with_email("[email protected]".into());

Pattern 6: Smart Pointers for Shared Ownership

use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;

// ✅ CORRECT: Rc for single-threaded shared ownership
fn shared_single_threaded() {
    let data = Rc::new(vec![1, 2, 3]);
    let data_clone = Rc::clone(&data);  // Cheap reference count increment

    println!("Count: {}", Rc::strong_count(&data));  // 2
    println!("{:?}", data);
    println!("{:?}", data_clone);
}

// ✅ CORRECT: Arc for multi-threaded shared ownership
use std::thread;

fn shared_multi_threaded() {
    let data = Arc::new(vec![1, 2, 3]);

    let handles: Vec<_> = (0..3).map(|i| {
        let data = Arc::clone(&data);
        thread::spawn(move || {
            println!("Thread {}: {:?}", i, data);
        })
    }).collect();

    for h in handles {
        h.join().unwrap();
    }
}

// ✅ CORRECT: RefCell for interior mutability
fn interior_mutability() {
    let data = RefCell::new(vec![1, 2, 3]);

    // Borrow rules checked at runtime
    data.borrow_mut().push(4);

    let borrowed = data.borrow();
    println!("{:?}", borrowed);
}

// Common combinations:
// Rc<RefCell<T>> - single-threaded shared mutable data
// Arc<Mutex<T>> - multi-threaded shared mutable data

Pattern 7: Avoiding Common Pitfalls

// ❌ WRONG: Returning reference to local variable
fn bad_return() -> &String {
    let s = String::from("hello");
    &s  // ERROR: s is dropped when function ends
}

// ✅ CORRECT: Return owned value
fn good_return() -> String {
    let s = String::from("hello");
    s  // Ownership transferred to caller
}

// ❌ WRONG: Moving out of borrowed content
fn bad_move(data: &Vec<String>) -> String {
    data[0]  // ERROR: cannot move out of borrowed content
}

// ✅ CORRECT: Clone or return reference
fn good_clone(data: &Vec<String>) -> String {
    data[0].clone()
}

fn good_ref(data: &Vec<String>) -> &String {
    &data[0]
}

// ❌ WRONG: Iterator invalidation
fn bad_iter() {
    let mut v = vec![1, 2, 3];
    for i in &v {
        v.push(*i * 2);  // ERROR: cannot borrow v as mutable
    }
}

// ✅ CORRECT: Collect first, then modify
fn good_iter() {
    let mut v = vec![1, 2, 3];
    let additions: Vec<_> = v.iter().map(|i| i * 2).collect();
    v.extend(additions);
}

When to Clone vs Borrow

// Clone when:
// 1. Data is small and Copy isn't implemented
// 2. You need independent ownership
// 3. Avoiding lifetime complexity is worth the cost

// Borrow when:
// 1. You only need to read the data
// 2. The data is large (avoid copying)
// 3. You're implementing a hot path
// 4. The data structure supports it

// Example: Config that's read frequently
struct App {
    config: Arc<Config>,  // Cheap to clone, shared read access
}

// Example: Large data processed once
fn process(data: &[u8]) -> Result<(), Error> {
    // Borrow - don't need ownership, data is large
}

Anti-Patterns

Don't: Clone to Avoid Learning Ownership

// ❌ BAD: Cloning everything to make compiler happy
fn process(data: Vec<String>) {
    let data2 = data.clone();  // Unnecessary
    for item in data {
        println!("{}", item);
    }
    // data2 unused
}

// ✅ GOOD: Use references appropriately
fn process(data: &[String]) {
    for item in data {
        println!("{}", item);
    }
}

Don't: Use 'static Lifetime Everywhere

// ❌ BAD: Forcing 'static unnecessarily
fn get_name() -> &'static str {
    let s = String::from("dynamic");
    Box::leak(Box::new(s))  // Memory leak!
}

// ✅ GOOD: Return owned or properly bounded reference
fn get_name() -> String {
    String::from("dynamic")
}

Resources

Topic Link
Ownership Deep Dive [mdc:resources/ownership.md]
Borrowing Patterns [mdc:resources/borrowing.md]
Lifetime Annotations [mdc:resources/lifetimes.md]
Smart Pointers [mdc:resources/smart-pointers.md]