home / skills / zhanghandong / rust-skills / m03-mutability

m03-mutability skill

/skills/m03-mutability

This skill helps you reason about Rust mutability, choose appropriate patterns, and prevent borrow conflicts across single and multi-thread contexts.

npx playbooks add skill zhanghandong/rust-skills --skill m03-mutability

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
4.1 KB
---
name: m03-mutability
description: "CRITICAL: Use for mutability issues. Triggers: E0596, E0499, E0502, cannot borrow as mutable, already borrowed as immutable, mut, &mut, interior mutability, Cell, RefCell, Mutex, RwLock, 可变性, 内部可变性, 借用冲突"
user-invocable: false
---

# Mutability

> **Layer 1: Language Mechanics**

## Core Question

**Why does this data need to change, and who can change it?**

Before adding interior mutability, understand:
- Is mutation essential or accidental complexity?
- Who should control mutation?
- Is the mutation pattern safe?

---

## Error → Design Question

| Error | Don't Just Say | Ask Instead |
|-------|----------------|-------------|
| E0596 | "Add mut" | Should this really be mutable? |
| E0499 | "Split borrows" | Is the data structure right? |
| E0502 | "Separate scopes" | Why do we need both borrows? |
| RefCell panic | "Use try_borrow" | Is runtime check appropriate? |

---

## Thinking Prompt

Before adding mutability:

1. **Is mutation necessary?**
   - Maybe transform → return new value
   - Maybe builder → construct immutably

2. **Who controls mutation?**
   - External caller → `&mut T`
   - Internal logic → interior mutability
   - Concurrent access → synchronized mutability

3. **What's the thread context?**
   - Single-thread → Cell/RefCell
   - Multi-thread → Mutex/RwLock/Atomic

---

## Trace Up ↑

When mutability conflicts persist:

```
E0499/E0502 (borrow conflicts)
    ↑ Ask: Is the data structure designed correctly?
    ↑ Check: m09-domain (should data be split?)
    ↑ Check: m07-concurrency (is async involved?)
```

| Persistent Error | Trace To | Question |
|-----------------|----------|----------|
| Repeated borrow conflicts | m09-domain | Should data be restructured? |
| RefCell in async | m07-concurrency | Is Send/Sync needed? |
| Mutex deadlocks | m07-concurrency | Is the lock design right? |

---

## Trace Down ↓

From design to implementation:

```
"Need mutable access from &self"
    ↓ T: Copy → Cell<T>
    ↓ T: !Copy → RefCell<T>

"Need thread-safe mutation"
    ↓ Simple counters → AtomicXxx
    ↓ Complex data → Mutex<T> or RwLock<T>

"Need shared mutable state"
    ↓ Single-thread: Rc<RefCell<T>>
    ↓ Multi-thread: Arc<Mutex<T>>
```

---

## Borrow Rules

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

Never both simultaneously.
```

## Quick Reference

| Pattern | Thread-Safe | Runtime Cost | Use When |
|---------|-------------|--------------|----------|
| `&mut T` | N/A | Zero | Exclusive mutable access |
| `Cell<T>` | No | Zero | Copy types, no refs needed |
| `RefCell<T>` | No | Runtime check | Non-Copy, need runtime borrow |
| `Mutex<T>` | Yes | Lock contention | Thread-safe mutation |
| `RwLock<T>` | Yes | Lock contention | Many readers, few writers |
| `Atomic*` | Yes | Minimal | Simple types (bool, usize) |

## Error Code Reference

| Error | Cause | Quick Fix |
|-------|-------|-----------|
| E0596 | Borrowing immutable as mutable | Add `mut` or redesign |
| E0499 | Multiple mutable borrows | Restructure code flow |
| E0502 | &mut while & exists | Separate borrow scopes |

---

## Interior Mutability Decision

| Scenario | Choose |
|----------|--------|
| T: Copy, single-thread | `Cell<T>` |
| T: !Copy, single-thread | `RefCell<T>` |
| T: Copy, multi-thread | `AtomicXxx` |
| T: !Copy, multi-thread | `Mutex<T>` or `RwLock<T>` |
| Read-heavy, multi-thread | `RwLock<T>` |
| Simple flags/counters | `AtomicBool`, `AtomicUsize` |

---

## Anti-Patterns

| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| RefCell everywhere | Runtime panics | Clear ownership design |
| Mutex for single-thread | Unnecessary overhead | RefCell |
| Ignore RefCell panic | Hard to debug | Handle or restructure |
| Lock inside hot loop | Performance killer | Batch operations |

---

## Related Skills

| When | See |
|------|-----|
| Smart pointer choice | m02-resource |
| Thread safety | m07-concurrency |
| Data structure design | m09-domain |
| Anti-patterns | m15-anti-pattern |

Overview

This skill helps diagnose and fix Rust mutability and interior-mutability issues. It focuses on compiler errors like E0596, E0499, E0502 and runtime RefCell panics, and guides you to safer design choices. Use it to decide when to add mutability, choose the right mutable container, or restructure data to avoid borrow conflicts.

How this skill works

I inspect error triggers and runtime symptoms and map them to design questions rather than quick fixes. The skill asks whether mutation is necessary, who should control it, and what thread context applies. It then recommends patterns (&mut, Cell, RefCell, Mutex, RwLock, Atomics) and points to restructuring or concurrency checks when borrows repeatedly collide.

When to use it

  • When the compiler reports E0596, E0499, or E0502
  • When you see RefCell borrow panics at runtime
  • When unsure whether to add mutability or use interior mutability
  • When choosing thread-safe vs single-thread mutable containers
  • When borrow conflicts suggest data should be restructured

Best practices

  • Ask if mutation is essential; prefer returning new values or builder patterns when possible
  • Decide who owns mutation: external callers (&mut) vs internal interior mutability vs synchronized concurrency
  • Match tool to context: Cell/RefCell for single-thread, Mutex/RwLock/Atomic for multi-thread
  • Trace persistent borrow errors to domain design — maybe split the data or change ownership
  • Avoid RefCell proliferation; prefer clear ownership and handle runtime borrow errors explicitly

Example use cases

  • Fix E0596: decide if adding mut is correct or if API should return an owned modified value
  • Resolve E0499/E0502: split data into separate fields or change call ordering to avoid overlapping borrows
  • Replace RefCell in async code with thread-safe primitives when Send/Sync is required
  • Implement a shared counter: use AtomicUsize for simple counts, Mutex for complex state
  • Switch single-thread mutable containers to RefCell/Cell to remove unnecessary locking

FAQ

Should I always add mut when compiler asks?

No. First confirm mutation is necessary and who should own it. Adding mut hides a design choice and can worsen borrow conflicts.

When to prefer RefCell vs Mutex?

Use RefCell for non-threaded interior mutability (runtime borrow checks). Use Mutex/RwLock when access must be thread-safe or Send/Sync is required.