Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill rust-ownership명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | rust-ownership |
| description | | Use when this capability is needed. |
The borrow checker is a design tool, not an obstacle. When it pushes back, rethink data ownership.
Ownership = exclusive control over data lifetime. Every value has one owner. When the owner is dropped, the value is freed. No GC, no double-free.
Ask before writing code:
| Error | Reflexive fix (wrong) | Right question |
|---|---|---|
| E0382 use of moved value | .clone() it | Who should own this? |
| E0597 doesn't live long enough | Add 'static | Is the scope boundary wrong? |
| E0506 cannot assign to borrowed | Re-borrow mutably | Should mutation happen here? |
| E0507 move out of borrowed | .clone() the field | Should the caller give ownership? |
| E0515 return reference to local | Return owned type | Should caller pass in a buffer? |
At any point in code: either
- ONE mutable reference (&mut T), or
- ANY NUMBER of immutable references (&T)
... but never both at the same time.
All references must be valid (no dangling pointers).
let mut data = vec![1, 2, 3];
// Multiple immutable borrows — OK
let r1 = &data;
let r2 = &data;
println!("{:?} {:?}", r1, r2); // r1 and r2 last used here
// Mutable borrow after immutable borrows END (NLL)
data.push(4); // OK: r1 and r2 no longer in use
// Copy types (stack-only, bitwise copy): i32, f64, bool, char, &T, [T; N] if T: Copy
let x: i32 = 5;
let y = x; // Copied, x still valid
println!("{x}"); // Fine
// Move types (heap data): String, Vec<T>, Box<T>
let s = String::from("hello");
let t = s; // MOVED — s is no longer valid
// println!("{s}"); // E0382!
// Clone: explicit deep copy
let s = String::from("hello");
let t = s.clone(); // Explicit copy, both valid
println!("{s} {t}");
Rule of thumb: If Copy doesn't make sense semantically (e.g., a file handle), use Clone only when you genuinely need two independent values.
// Bad: Cloning just to pass to a read-only function
fn print_name(name: String) { println!("{name}"); }
print_name(user.name.clone()); // Wasteful
// Good: Borrow instead
fn print_name(name: &str) { println!("{name}"); }
print_name(&user.name); // Zero cost
// Good: Accept impl AsRef<str> for maximum flexibility
fn print_name(name: impl AsRef<str>) { println!("{}", name.as_ref()); }
print_name("literal"); // &str
print_name(&user.name); // &String → auto-deref to &str
print_name(user.name); // Owned String
Lifetimes tell the compiler how long references are valid. They're inferred in most cases (lifetime elision), but must be explicit when:
// Elision works: compiler knows output comes from input
fn first(s: &str) -> &str {
&s[..1] // lifetime of return = lifetime of s
}
// Must annotate: which input does the output borrow from?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 'a = the shorter of x's and y's lifetime
// Struct holding a reference
struct Excerpt<'a> {
text: &'a str, // Can't outlive the string it came from
}
// 'static: lives for the entire program (string literals, leaked data)
fn get_greeting() -> &'static str {
"Hello!" // String literals are 'static
}
// Returning owned value — avoids lifetime complexity
fn build_greeting(name: &str) -> String {
format!("Hello, {name}!")
}
// Output lifetime from self (method pattern)
impl Cache {
fn get(&self, key: &str) -> Option<&str> {
// return lifetime = self's lifetime (elision rule 3)
self.map.get(key).map(String::as_str)
}
}
Use Cow<'_, T> when a function sometimes needs to allocate and sometimes doesn't:
use std::borrow::Cow;
fn normalize_username(name: &str) -> Cow<'_, str> {
if name.chars().all(|c| c.is_lowercase()) {
Cow::Borrowed(name) // No allocation needed
} else {
Cow::Owned(name.to_lowercase()) // Allocates only when necessary
}
}
// Caller doesn't care which variant they got
let n = normalize_username("Alice");
println!("{n}"); // Works for both Borrowed and Owned
When you need mutation through a shared reference:
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
// Single-threaded: RefCell (runtime borrow checking)
let data = RefCell::new(vec![1, 2, 3]);
data.borrow_mut().push(4); // Panics if already mutably borrowed
// Multi-threaded: Arc<Mutex<T>>
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let data2 = Arc::clone(&data);
std::thread::spawn(move || {
data2.lock().unwrap().push(4);
});
// Read-heavy: Arc<RwLock<T>>
use std::sync::RwLock;
let data = Arc::new(RwLock::new(HashMap::new()));
let r = data.read().unwrap(); // Many readers at once
let mut w = data.write().();
Avoid self-referential structs — they're a borrow checker nightmare. Instead:
// Bad: struct referencing its own field
struct SelfRef {
data: String,
// ptr: &'??? str, // What lifetime? It references `data`!
}
// Good option 1: Store indices instead of references
struct Parser {
input: String,
pos: usize, // Position into input
}
// Good option 2: Pin + unsafe (for truly needed cases)
use std::pin::Pin;
// Use `pin-project` crate for safe pinned projections
// Good option 3: Separate lifetime (caller owns the data)
struct Parser<'a> {
input: &'a str, // Caller's data, parser borrows it
pos: usize,
}
// E0382: Use of moved value
// Problem: moved into a closure, then used again
let name = String::from("Alice");
let greeting = move || println!("{name}");
// println!("{name}"); // ERROR
// Fix: clone before the move
let name = String::from("Alice");
let name2 = name.clone();
let greeting = move || println!("{name}");
println!("{name2}"); // OK
// E0502: Can't borrow mutably while immutably borrowed
let mut v = vec![1, 2, 3];
let first = v[0]; // Copy the value (i32 is Copy)
v.push(4); // OK: no borrow active
println!("{first}");
// E0597: Borrowed value doesn't live long enough
// Problem: returning reference to local
fn bad() & {
= ::();
&s
}
() {
::()
}
Use std::borrow::Cow (Clone-on-Write) when a function can accept either a borrowed or owned representation, only allocating when write/mutation is required.
use std::borrow::Cow;
fn sanitize_username<'a>(username: &'a str) -> Cow<'a, str> {
if username.chars().all(|c| c.is_lowercase()) {
Cow::Borrowed(username) // Zero allocation
} else {
Cow::Owned(username.to_lowercase()) // Allocates only when uppercase exists
}
}
Use interior mutability structures to allow mutating data through immutable (&T) references.
RefCell<T> (checked at runtime; panics on dynamic borrow conflicts).Mutex<T> (blocks threads dynamically) or RwLock<T> (if reads dominate writes).Cell<T>) for simple copyable primitives (i32, bool) to bypass borrow checker checks entirely.&T or &str?Cow eliminate the clone in the common path?Source: adxptived/Rust-Skills — distributed by TomeVault.