Agent Skills: Check compilation

Interior mutability expert covering Cell, RefCell, Mutex, RwLock patterns, borrow conflicts (E0596, E0499, E0502), and thread-safe mutation strategies.

UncategorizedID: huiali/rust-skills/rust-mutability

Install this agent skill to your local

pnpm dlx add-skill https://github.com/huiali/rust-skills/tree/HEAD/.codex/skills/rust-mutability

Skill Files

Browse the full folder contents for rust-mutability.

Download Skill

Loading file tree…

.codex/skills/rust-mutability/SKILL.md

Skill Metadata

Name
rust-mutability
Description
Interior mutability expert covering Cell, RefCell, Mutex, RwLock patterns, borrow conflicts (E0596, E0499, E0502), and thread-safe mutation strategies.

Mutability Types

| Type | Controller | Thread-Safe | Use Case | |------|------------|-------------|----------| | &mut T | External caller | Yes | Standard mutable borrow | | Cell<T> | Interior | No | Copy types with interior mutability | | RefCell<T> | Interior | No | Non-Copy types with interior mutability | | Mutex<T> | Interior | Yes | Multi-threaded interior mutability | | RwLock<T> | Interior | Yes | Multi-threaded read-write lock |

Solution Patterns

Pattern 1: External Mutability

// Standard mutable borrow
fn increment(counter: &mut u32) {
    *counter += 1;
}

// Mutable method
impl Counter {
    fn increment(&mut self) {
        self.value += 1;
    }
}

When to use: Default choice, mutability controlled by caller.

Pattern 2: Cell for Copy Types

use std::cell::Cell;

struct State {
    count: Cell<u32>,
}

impl State {
    // Get immutable &self, mutate interior
    fn increment(&self) {
        self.count.set(self.count.get() + 1);
    }
}

When to use: Simple values (Copy types) need interior mutability.

Trade-offs: Only works with Copy types, no references.

Pattern 3: RefCell for Non-Copy Types

use std::cell::RefCell;

struct Cache {
    data: RefCell<HashMap<String, Value>>,
}

impl Cache {
    fn insert(&self, key: String, value: Value) {
        self.data.borrow_mut().insert(key, value);
    }

    fn get(&self, key: &str) -> Option<Value> {
        self.data.borrow().get(key).cloned()
    }
}

When to use: Need &mut T from &self, single-threaded.

Trade-offs: Runtime borrow checking, can panic.

Pattern 4: Mutex for Thread Safety

use std::sync::Mutex;

struct SharedState {
    data: Mutex<HashMap<String, Value>>,
}

impl SharedState {
    fn insert(&self, key: String, value: Value) {
        self.data.lock().unwrap().insert(key, value);
    }
}

When to use: Multi-threaded interior mutability.

Trade-offs: Lock contention, can deadlock.

Pattern 5: RwLock for Read-Heavy Workloads

use std::sync::RwLock;

struct Config {
    settings: RwLock<HashMap<String, String>>,
}

impl Config {
    fn get(&self, key: &str) -> Option<String> {
        self.settings.read().unwrap().get(key).cloned()
    }

    fn update(&self, key: String, value: String) {
        self.settings.write().unwrap().insert(key, value);
    }
}

When to use: Many readers, few writers.

Trade-offs: Write locks more expensive than Mutex.

Borrow Rules

At any time, you can have either:
├─ Multiple &T (immutable borrows)
└─ OR one &mut T (mutable borrow)

Never both simultaneously

Error Code Quick Reference

| Code | Meaning | Don't Say | Ask Instead | |------|---------|-----------|-------------| | E0596 | Cannot get mutable reference | "add mut" | Does this really need mutability? | | E0499 | Multiple mutable borrows conflict | "split borrows" | Is data structure design correct? | | E0502 | Borrow conflict | "separate scopes" | Why both borrows needed simultaneously? | | RefCell panic | Runtime borrow error | "use try_borrow" | Is runtime checking appropriate? |

Workflow

Step 1: Choose Mutability Strategy

Single-threaded?
  Need &mut from &self?
    → RefCell<T>
  Copy type?
    → Cell<T>
  Otherwise?
    → &mut T

Multi-threaded?
  Simple atomic?
    → AtomicU64/AtomicBool
  Complex data?
    Read-heavy → RwLock<T>
    Write-heavy → Mutex<T>

Step 2: Handle Borrow Conflicts

E0499 (multiple mut borrows)?
  → Split struct into smaller pieces
  → Use Cell/RefCell for interior mutability
  → Redesign to avoid simultaneous access

E0502 (borrow conflict)?
  → Minimize borrow scopes
  → Clone data if needed
  → Restructure code flow

Step 3: Consider Trade-offs

RefCell?
  ✅ Flexible
  ❌ Runtime panics possible
  → Use in prototypes, single-threaded

Mutex?
  ✅ Thread-safe
  ❌ Lock contention
  → Profile before optimizing

RwLock?
  ✅ Many readers efficient
  ❌ Writer starvation possible
  → Use when reads >> writes

Thread-Safe Selection

Atomic Types

use std::sync::atomic::{AtomicU64, Ordering};

let counter = AtomicU64::new(0);
counter.fetch_add(1, Ordering::Relaxed);

Use when: Simple counters, flags.

Mutex

use std::sync::Mutex;

let data = Mutex::new(HashMap::new());
data.lock().unwrap().insert(key, value);

Use when: Thread-safe mutation, balanced read/write.

RwLock

use std::sync::RwLock;

let data = RwLock::new(HashMap::new());
data.read().unwrap().get(&key);  // Many readers
data.write().unwrap().insert(key, value);  // Few writers

Use when: Read-heavy workloads (10+ reads per write).

Common Pitfalls

1. Borrow Conflict

Symptom: E0499, E0502 errors

// ❌ Bad: multiple mutable borrows
let r1 = &mut data.field1;
let r2 = &mut data.field2;  // Error!

// ✅ Good: split borrows
let (field1, field2) = (&mut data.field1, &mut data.field2);

// ✅ Better: restructure
struct Data {
    part1: Part1,
    part2: Part2,
}

2. RefCell Panic

Symptom: "already borrowed" panic at runtime

// ❌ Bad: nested borrows
let cell = RefCell::new(vec![1, 2, 3]);
let borrow1 = cell.borrow();
let borrow2 = cell.borrow_mut();  // Panics!

// ✅ Good: drop first borrow
{
    let borrow1 = cell.borrow();
    // use borrow1...
}  // dropped
let borrow2 = cell.borrow_mut();  // OK

// ✅ Better: use try_borrow
if let Ok(mut b) = cell.try_borrow_mut() {
    // safe mutation
}

3. Lock Held Across Await

Symptom: Deadlock in async code

// ❌ Bad: MutexGuard across await
let guard = mutex.lock().unwrap();
async_op().await;  // DANGER

// ✅ Good: drop lock before await
let value = {
    let guard = mutex.lock().unwrap();
    guard.clone()
};  // lock dropped
async_op().await;

Review Checklist

When reviewing mutability code:

  • [ ] Mutability truly necessary (not premature)
  • [ ] Appropriate mutability type chosen (Cell/RefCell/Mutex)
  • [ ] RefCell used only in single-threaded contexts
  • [ ] Mutex/RwLock used for multi-threaded access
  • [ ] Lock scopes minimized to avoid contention
  • [ ] No locks held across .await points
  • [ ] Borrow conflicts resolved at design level
  • [ ] Runtime panics handled (try_borrow)
  • [ ] Atomic types used for simple counters/flags
  • [ ] Read-write patterns match RwLock choice

Verification Commands

# Check compilation
cargo check

# Look for borrow conflict errors
cargo check 2>&1 | grep -E "E0499|E0502|E0596"

# Run tests
cargo test

# Check for deadlocks (with loom)
cargo test --features loom

# Clippy warnings
cargo clippy -- -W clippy::mutex_atomic

Advanced Patterns

Splitting Borrows

// ✅ Split struct to enable simultaneous borrows
struct Data {
    readers: Vec<Reader>,
    writers: Vec<Writer>,
}

fn process(data: &mut Data) {
    let readers = &data.readers;
    let writers = &mut data.writers;  // OK, different fields
    // use both...
}

Interior Mutability with Shared Ownership

use std::sync::{Arc, Mutex};

#[derive(Clone)]
struct Shared {
    inner: Arc<Mutex<Inner>>,
}

impl Shared {
    fn update(&self) {
        self.inner.lock().unwrap().modify();
    }
}

Related Skills

  • rust-ownership - Ownership and borrowing fundamentals
  • rust-concurrency - Thread-safe patterns
  • rust-unsafe - UnsafeCell and low-level mutability
  • rust-anti-pattern - Mutability anti-patterns
  • rust-performance - Lock contention optimization

Localized Reference

  • Chinese version: SKILL_ZH.md - 完整中文版本,包含所有内容