AgentSkillsCN

rust-ownership

所有权、借用和生命周期模式

SKILL.md
--- frontmatter
name: rust-ownership
description: Ownership, borrowing, and lifetime patterns
version: 1.0.0
triggers:
  - ownership
  - borrow
  - lifetime
  - move
  - clone
  - rust memory
  - borrow checker

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

ConceptRuleExample
OwnershipOne owner at a timelet s2 = s1; (s1 moved)
BorrowingMultiple &T OR one &mut T&data or &mut data
LifetimesReferences must not outlive data'a, 'static
CloneExplicit deep copydata.clone()
CopyImplicit 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

rust
// ✅ 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

rust
// ✅ 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

rust
// ✅ 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

rust
// ✅ 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

rust
// ✅ 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(), "alice@example.com".into(), 30)
    .with_email("alice.new@example.com".into());

Pattern 6: Smart Pointers for Shared Ownership

rust
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

rust
// ❌ 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

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

rust
// ❌ 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

rust
// ❌ 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

TopicLink
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]