
M14 Mental Model
- 764 installs
- 1.3k repo stars
- Updated May 24, 2026
- zhanghandong/rust-skills
This is a copy of m14-mental-model by actionbook - installs and ranking accrue to the original listing.
m14-mental-model is an agent skill that reframes Java-style reference thinking into Rust ownership, borrowing, and lifetime mental models before writing or reviewing code.
About
m14-mental-model is Layer 2 of the zhanghandong rust-skills collection, focused on how to think about Rust concepts rather than syntax alone. The skill maps eight core models—ownership as a unique key, move as key handover, &T as read-only lending, &mut T as exclusive editing, lifetimes as valid scope tickets, Box as heap remote control, Rc as shared ownership, and Arc as thread-safe Rc—with stack-and-heap ASCII diagrams. It contrasts shifts from Java, C#, C++, Python, Go, and JavaScript, listing five common compiler errors (E0382, E0502, E0499, E0106, E0507) with wrong versus correct mental models. A thinking prompt asks who owns data, what guarantee Rust enforces, and what the compiler error means, with trace-up links to m01–m07 skills. Reach for m14-mental-model when developers coming from GC languages hit borrow-checker confusion before diving into implementation skills like m01-ownership.
- Three core models: ownership as single-owner cleanup, borrowing as temporary access, lifetimes as validity scopes
- Concrete snippets for String ownership, &String borrows, and explicit 'a lifetime on longest
- Contrasts Rust with Java/C# everything-is-a-reference habits to reduce fight-the-borrow-checker loops
- Structured as repeatable perspective shifts for debugging and API design
M14 Mental Model by the numbers
- 764 all-time installs (skills.sh)
- +7 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zhanghandong/rust-skills --skill m14-mental-modelAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 764 |
|---|---|
| repo stars | ★ 1.3k |
| Security audit | 3 / 3 scanners passed |
| Last updated | May 24, 2026 |
| Repository | zhanghandong/rust-skills ↗ |
How do you understand Rust ownership and borrowing?
Reframe Java-style reference thinking into Rust ownership, borrowing, and lifetimes before writing or reviewing Rust code.
Who is it for?
Developers migrating from Java, Python, or Go who need intuition for Rust ownership before implementing services or reviewing borrow-checker errors.
Skip if: Advanced Rust engineers who already internalize ownership patterns and only need domain-specific unsafe or async guidance.
When should I use this skill?
The user is confused about Rust ownership, borrow checker errors, lifetimes, or asks to explain Rust concepts coming from another language.
What you get
Correct mental models for ownership moves, borrows, lifetimes, and smart pointers plus mapped fixes for common borrow-checker errors.
By the numbers
- Documents 8 core mental models in the Key Mental Models table
- Maps 5 common compiler errors to misconception corrections
- Includes 5 language migration rows in Coming From Other Languages
Files
Mental Models
Layer 2: Design Choices
Core Question
What's the right way to think about this Rust concept?
When learning or explaining Rust:
- What's the correct mental model?
- What misconceptions should be avoided?
- What analogies help understanding?
---
Key Mental Models
| Concept | Mental Model | Analogy |
|---|---|---|
| Ownership | Unique key | Only one person has the house key |
| Move | Key handover | Giving away your key |
&T | Lending for reading | Lending a book |
&mut T | Exclusive editing | Only you can edit the doc |
Lifetime 'a | Valid scope | "Ticket valid until..." |
Box<T> | Heap pointer | Remote control to TV |
Rc<T> | Shared ownership | Multiple remotes, last turns off |
Arc<T> | Thread-safe Rc | Remotes from any room |
---
Coming From Other Languages
| From | Key Shift |
|---|---|
| Java/C# | Values are owned, not references by default |
| C/C++ | Compiler enforces safety rules |
| Python/Go | No GC, deterministic destruction |
| Functional | Mutability is safe via ownership |
| JavaScript | No null, use Option instead |
---
Thinking Prompt
When confused about Rust:
1. What's the ownership model?
- Who owns this data?
- How long does it live?
- Who can access it?
2. What guarantee is Rust providing?
- No data races
- No dangling pointers
- No use-after-free
3. What's the compiler telling me?
- Error = violation of safety rule
- Solution = work with the rules
---
Trace Up ↑
To design understanding (Layer 2):
"Why can't I do X in Rust?"
↑ Ask: What safety guarantee would be violated?
↑ Check: m01-m07 for the rule being enforced
↑ Ask: What's the intended design pattern?---
Trace Down ↓
To implementation (Layer 1):
"I understand the concept, now how do I implement?"
↓ m01-ownership: Ownership patterns
↓ m02-resource: Smart pointer choice
↓ m07-concurrency: Thread safety---
Common Misconceptions
| Error | Wrong Model | Correct Model |
|---|---|---|
| E0382 use after move | GC cleans up | Ownership = unique key transfer |
| E0502 borrow conflict | Multiple writers OK | Only one writer at a time |
| E0499 multiple mut borrows | Aliased mutation | Exclusive access for mutation |
| E0106 missing lifetime | Ignoring scope | References have validity scope |
E0507 cannot move from &T | Implicit clone | References don't own data |
Deprecated Thinking
| Deprecated | Better |
|---|---|
| "Rust is like C++" | Different ownership model |
| "Lifetimes are GC" | Compile-time validity scope |
| "Clone solves everything" | Restructure ownership |
| "Fight the borrow checker" | Work with the compiler |
"unsafe to avoid rules" | Understand safe patterns first |
---
Ownership Visualization
Stack Heap
+----------------+ +----------------+
| main() | | |
| s1 ─────────────────────> │ "hello" |
| | | |
| fn takes(s) { | | |
| s2 (moved) ─────────────> │ "hello" |
| } | | (s1 invalid) |
+----------------+ +----------------+
After move: s1 is no longer validReference Visualization
+----------------+
| data: String |────────────> "hello"
+----------------+
↑
│ &data (immutable borrow)
│
+------+------+
| reader1 reader2 (multiple OK)
+------+------+
+----------------+
| data: String |────────────> "hello"
+----------------+
↑
│ &mut data (mutable borrow)
│
+------+
| writer (only one)
+------+---
Learning Path
| Stage | Focus | Skills |
|---|---|---|
| Beginner | Ownership basics | m01-ownership, m14-mental-model |
| Intermediate | Smart pointers, error handling | m02, m06 |
| Advanced | Concurrency, unsafe | m07, unsafe-checker |
| Expert | Design patterns | m09-m15, domain-* |
---
Related Skills
| When | See |
|---|---|
| Ownership errors | m01-ownership |
| Smart pointers | m02-resource |
| Concurrency | m07-concurrency |
| Anti-patterns | m15-anti-pattern |
Thinking in Rust: Mental Models
Core Mental Models
1. Ownership as Resource Management
Traditional: "Who has a pointer to this data?"
Rust: "Who OWNS this data and is responsible for freeing it?"Key insight: Every value has exactly one owner. When the owner goes out of scope, the value is dropped.
{
let s = String::from("hello"); // s owns the String
// use s...
} // s goes out of scope, String is dropped (memory freed)2. Borrowing as Temporary Access
Traditional: "I'll just read from this pointer"
Rust: "I'm borrowing this value, owner still responsible for it"Key insight: Borrows are like library books - you can read them, but must return them.
fn print_length(s: &String) { // borrows s
println!("{}", s.len());
} // borrow ends, caller still owns s
let my_string = String::from("hello");
print_length(&my_string); // lend to function
println!("{}", my_string); // still have it3. Lifetimes as Validity Scopes
Traditional: "Hope this pointer is still valid"
Rust: "Compiler tracks exactly how long references are valid"Key insight: A reference can't outlive the data it points to.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// 'a means: the returned reference is valid as long as BOTH inputs are valid
if x.len() > y.len() { x } else { y }
}---
Shifting Perspectives
From "Everything is a Reference" (Java/C#)
Java mental model:
// Everything is implicitly a reference
User user = new User("Alice"); // user is a reference
List<User> users = new ArrayList<>();
users.add(user); // shares the reference
user.setName("Bob"); // affects the list too!Rust mental model:
// Values are owned, sharing is explicit
let user = User::new("Alice"); // user is owned
let mut users = vec![];
users.push(user); // user moved into vec, can't use user anymore
// user.set_name("Bob"); // ERROR: user was moved
// If you need sharing:
use std::rc::Rc;
let user = Rc::new(User::new("Alice"));
let user2 = Rc::clone(&user); // explicit shared ownershipFrom "Manual Memory Management" (C/C++)
C mental model:
char* s = malloc(100);
// ... must remember to free(s) ...
// ... what if we return early? ...
// ... what if an exception occurs? ...
free(s);Rust mental model:
let s = String::with_capacity(100);
// ... use s ...
// No need to free - Rust drops s automatically when scope ends
// Even with early returns, panics, or any control flowFrom "Garbage Collection" (Go/Python)
GC mental model:
# Create objects, GC will figure it out
users = []
for name in names:
users.append(User(name))
# GC runs sometime later, when it feels like itRust mental model:
let users: Vec<User> = names
.iter()
.map(|name| User::new(name))
.collect();
// Memory is freed EXACTLY when users goes out of scope
// Deterministic, no GC pauses, no unpredictable memory usage---
Key Questions to Ask
When Designing Functions
1. Does this function need to own the data, or just read it?
- Need to keep it: take ownership (
fn process(data: Vec<T>)) - Just reading: borrow (
fn process(data: &[T])) - Need to modify: mutable borrow (
fn process(data: &mut Vec<T>))
2. Does the return value contain references to inputs?
- Yes: need lifetime annotations
- No: lifetime elision usually works
When Designing Structs
1. Should this struct own its data or reference it?
- Long-lived, independent: own (
name: String) - Short-lived view: reference (
name: &'a str)
2. Do multiple parts need to access the same data?
- Single-threaded:
Rc<T>orRc<RefCell<T>> - Multi-threaded:
Arc<T>orArc<Mutex<T>>
When Hitting Borrow Checker Errors
1. Am I trying to use a value after moving it?
- Clone it, borrow it, or restructure the code
2. Am I trying to have multiple mutable references?
- Scope the mutations, use interior mutability, or redesign
3. Does a reference outlive its source?
- Return owned data instead, or use
'static
---
Common Patterns
The Clone Escape Hatch
When fighting the borrow checker, .clone() often works:
// Can't do this - double borrow
let mut map = HashMap::new();
for key in map.keys() {
map.insert(key.clone(), process(key)); // ERROR: map borrowed twice
}
// Clone to escape
let keys: Vec<_> = map.keys().cloned().collect();
for key in keys {
map.insert(key.clone(), process(&key)); // OK
}But ask: "Is there a better design?" Often, restructuring is better than cloning.
The "Make It Own" Pattern
When lifetimes get complex, make the struct own its data:
// Complex: struct with references
struct Parser<'a> {
input: &'a str,
current: &'a str,
}
// Simpler: struct owns data
struct Parser {
input: String,
position: usize,
}The "Split the Borrow" Pattern
struct Data {
field_a: Vec<i32>,
field_b: Vec<i32>,
}
// Can't borrow self mutably twice
fn process(&mut self) {
// for a in &self.field_a {
// self.field_b.push(*a); // ERROR
// }
// Split the borrow
let Data { field_a, field_b } = self;
for a in field_a.iter() {
field_b.push(*a); // OK: separate borrows
}
}---
The Rust Way
Embrace the Type System
// Don't: stringly-typed
fn connect(host: &str, port: &str) { ... }
connect("8080", "localhost"); // oops, wrong order
// Do: strongly-typed
struct Host(String);
struct Port(u16);
fn connect(host: Host, port: Port) { ... }
// connect(Port(8080), Host("localhost".into())); // compile error!Make Invalid States Unrepresentable
// Don't: runtime checks
struct Connection {
socket: Option<Socket>,
connected: bool,
}
// Do: types enforce states
enum Connection {
Disconnected,
Connected { socket: Socket },
}Let the Compiler Guide You
// Start with what you want
fn process(data: ???) -> ???
// Let compiler errors tell you:
// - What types are needed
// - What lifetimes are needed
// - What bounds are needed
// The error messages are documentation!---
Summary: The Rust Mental Model
1. Values have owners - exactly one at a time 2. Borrowing is lending - temporary access, owner retains responsibility 3. Lifetimes are scopes - compiler tracks validity 4. Types encode constraints - use them to prevent bugs 5. The compiler is your friend - work with it, not against it
When stuck:
- Clone to make progress
- Restructure to own instead of borrow
- Ask: "What is the compiler trying to tell me?"
Related skills
How it compares
Pick m14-mental-model for conceptual reframing; invoke m01-ownership when you need concrete Rust ownership implementation patterns.
FAQ
What mental models does m14-mental-model teach?
m14-mental-model teaches eight models—ownership as unique key, move as handover, &T and &mut T as lending, lifetimes as scope tickets, and Box, Rc, Arc as heap and shared ownership patterns—with language migration tables.
Which Rust compiler errors does m14-mental-model address?
m14-mental-model maps five frequent errors—E0382 use after move, E0502 borrow conflict, E0499 multiple mut borrows, E0106 missing lifetime, E0507 move from &T—to misconception tables with corrected thinking.
What rust-skills layer is m14-mental-model?
m14-mental-model is Layer 2 Design Choices in zhanghandong/rust-skills, bridging conceptual understanding before Layer 1 implementation skills like m01-ownership and m02-resource.
Is M14 Mental Model safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.