
M01 Ownership
- 822 installs
- 1.3k repo stars
- Updated May 24, 2026
- zhanghandong/rust-skills
This is a copy of m01-ownership by actionbook - installs and ranking accrue to the original listing.
m01-ownership is an agent skill that teaches Rust ownership, borrowing, and move semantics by comparing them directly with C++ and Go for developers writing safe concurrent code.
About
m01-ownership is a Rust learning skill that explains ownership rules through side-by-side comparisons with C++ and Go. It contrasts Rust move semantics, where `let b = a` invalidates `a`, with C++ `std::move` behavior and Go garbage collection, and shows when explicit `clone()` is required versus implicit copies. The skill uses compile-error examples, memory-management tables, and concurrent-code scenarios so developers coming from C++ or Go can reason about borrow checker errors instead of fighting them. Reach for it when ownership, lifetimes, or move/copy behavior block progress on Rust backend or CLI code.
- Side-by-side memory management tables contrasting Rust move semantics with C++ copy/move and Go garbage collection
- Concrete code examples showing why moved values become invalid in Rust but compile with undefined behavior in C++
- Smart pointer equivalence mapping: Box<T> vs unique_ptr, Rc<T>/Arc<T> vs shared_ptr, RefCell<T> vs manual checks
- Explicit Send/Sync trait guidance for safe data sharing across threads
- Ownership rules that prevent null dereferences and data races at compile time
M01 Ownership by the numbers
- 822 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 m01-ownershipAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 822 |
|---|---|
| repo stars | ★ 1.3k |
| Security audit | 3 / 3 scanners passed |
| Last updated | May 24, 2026 |
| Repository | zhanghandong/rust-skills ↗ |
How does Rust ownership differ from C++ and Go?
Quickly grasp Rust ownership rules by comparing them directly with C++ and Go while writing safe concurrent code.
Who is it for?
Developers moving from C++ or Go who need a fast, comparative explanation of Rust ownership before writing production Rust code.
Skip if: Experienced Rust developers who already internalize borrowing and only need advanced lifetime or async trait guidance.
When should I use this skill?
A developer hits borrow checker errors or asks how Rust ownership compares to C++, Go, or manual memory management.
What you get
Ownership mental model, move versus copy examples, and borrow-safe concurrent code patterns
- ownership comparison notes
- move and borrow examples
- concurrent-safe coding patterns
Files
Ownership & Lifetimes
Layer 1: Language Mechanics
Core Question
Who should own this data, and for how long?
Before fixing ownership errors, understand the data's role:
- Is it shared or exclusive?
- Is it short-lived or long-lived?
- Is it transformed or just read?
---
Error → Design Question
| Error | Don't Just Say | Ask Instead |
|---|---|---|
| E0382 | "Clone it" | Who should own this data? |
| E0597 | "Extend lifetime" | Is the scope boundary correct? |
| E0506 | "End borrow first" | Should mutation happen elsewhere? |
| E0507 | "Clone before move" | Why are we moving from a reference? |
| E0515 | "Return owned" | Should caller own the data? |
| E0716 | "Bind to variable" | Why is this temporary? |
| E0106 | "Add 'a" | What is the actual lifetime relationship? |
---
Thinking Prompt
Before fixing an ownership error, ask:
1. What is this data's domain role?
- Entity (unique identity) → owned
- Value Object (interchangeable) → clone/copy OK
- Temporary (computation result) → maybe restructure
2. Is the ownership design intentional?
- By design → work within constraints
- Accidental → consider redesign
3. Fix symptom or redesign?
- If Strike 3 (3rd attempt) → escalate to Layer 2
---
Trace Up ↑
When errors persist, trace to design layer:
E0382 (moved value)
↑ Ask: What design choice led to this ownership pattern?
↑ Check: m09-domain (is this Entity or Value Object?)
↑ Check: domain-* (what constraints apply?)| Persistent Error | Trace To | Question |
|---|---|---|
| E0382 repeated | m02-resource | Should use Arc/Rc for sharing? |
| E0597 repeated | m09-domain | Is scope boundary at right place? |
| E0506/E0507 | m03-mutability | Should use interior mutability? |
---
Trace Down ↓
From design decisions to implementation:
"Data needs to be shared immutably"
↓ Use: Arc<T> (multi-thread) or Rc<T> (single-thread)
"Data needs exclusive ownership"
↓ Use: move semantics, take ownership
"Data is read-only view"
↓ Use: &T (immutable borrow)---
Quick Reference
| Pattern | Ownership | Cost | Use When |
|---|---|---|---|
| Move | Transfer | Zero | Caller doesn't need data |
&T | Borrow | Zero | Read-only access |
&mut T | Exclusive borrow | Zero | Need to modify |
clone() | Duplicate | Alloc + copy | Actually need a copy |
Rc<T> | Shared (single) | Ref count | Single-thread sharing |
Arc<T> | Shared (multi) | Atomic ref count | Multi-thread sharing |
Cow<T> | Clone-on-write | Alloc if mutated | Might modify |
Error Code Reference
| Error | Cause | Quick Fix |
|---|---|---|
| E0382 | Value moved | Clone, reference, or redesign ownership |
| E0597 | Reference outlives owner | Extend owner scope or restructure |
| E0506 | Assign while borrowed | End borrow before mutation |
| E0507 | Move out of borrowed | Clone or use reference |
| E0515 | Return local reference | Return owned value |
| E0716 | Temporary dropped | Bind to variable |
| E0106 | Missing lifetime | Add 'a annotation |
---
Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|---|---|---|
.clone() everywhere | Hides design issues | Design ownership properly |
| Fight borrow checker | Increases complexity | Work with the compiler |
'static for everything | Restricts flexibility | Use appropriate lifetimes |
Leak with Box::leak | Memory leak | Proper lifetime design |
---
Related Skills
| When | See |
|---|---|
| Need smart pointers | m02-resource |
| Need interior mutability | m03-mutability |
| Data is domain entity | m09-domain |
| Learning ownership concepts | m14-mental-model |
Ownership: Comparison with Other Languages
Rust vs C++
Memory Management
| Aspect | Rust | C++ |
|---|---|---|
| Default | Move semantics | Copy semantics (pre-C++11) |
| Move | let b = a; (a invalidated) | auto b = std::move(a); (a valid but unspecified) |
| Copy | let b = a.clone(); | auto b = a; |
| Safety | Compile-time enforcement | Runtime responsibility |
Rust Move vs C++ Move
// Rust: after move, 'a' is INVALID
let a = String::from("hello");
let b = a; // a moved
// println!("{}", a); // COMPILE ERROR
// Equivalent in C++:
// std::string a = "hello";
// std::string b = std::move(a);
// std::cout << a; // UNDEFINED (compiles but buggy)Smart Pointers
| Rust | C++ | Purpose |
|---|---|---|
Box<T> | std::unique_ptr<T> | Unique ownership |
Rc<T> | std::shared_ptr<T> | Shared ownership |
Arc<T> | std::shared_ptr<T> + atomic | Thread-safe shared |
RefCell<T> | (manual runtime checks) | Interior mutability |
---
Rust vs Go
Memory Model
| Aspect | Rust | Go |
|---|---|---|
| Memory | Stack + heap, explicit | GC manages all |
| Ownership | Enforced at compile-time | None (GC handles) |
| Null | Option<T> | nil for pointers |
| Concurrency | Send/Sync traits | Channels (less strict) |
Sharing Data
// Rust: explicit about sharing
use std::sync::Arc;
let data = Arc::new(vec![1, 2, 3]);
let data_clone = Arc::clone(&data);
std::thread::spawn(move || {
println!("{:?}", data_clone);
});
// Go: implicit sharing
// data := []int{1, 2, 3}
// go func() {
// fmt.Println(data) // potential race condition
// }()Why No GC in Rust
1. Deterministic destruction: Resources freed exactly when scope ends 2. Zero-cost: No GC pauses or overhead 3. Embeddable: Works in OS kernels, embedded systems 4. Predictable latency: Critical for real-time systems
---
Rust vs Java/C#
Reference Semantics
| Aspect | Rust | Java/C# |
|---|---|---|
| Objects | Owned by default | Reference by default |
| Null | Option<T> | null (nullable) |
| Immutability | Default | Must use final/readonly |
| Copy | Explicit .clone() | Reference copy (shallow) |
Comparison
// Rust: clear ownership
fn process(data: Vec<i32>) { // takes ownership
// data is ours, will be freed at end
}
let numbers = vec![1, 2, 3];
process(numbers);
// numbers is invalid here
// Java: ambiguous ownership
// void process(List<Integer> data) {
// // Who owns data? Caller? Callee? Both?
// // Can caller still use it?
// }---
Rust vs Python
Memory Model
| Aspect | Rust | Python |
|---|---|---|
| Typing | Static, compile-time | Dynamic, runtime |
| Memory | Ownership-based | Reference counting + GC |
| Mutability | Default immutable | Default mutable |
| Performance | Native, zero-cost | Interpreted, higher overhead |
Common Pattern Translation
// Rust: borrowing iteration
let items = vec!["a", "b", "c"];
for item in &items {
println!("{}", item);
}
// items still usable
// Python: iteration doesn't consume
// items = ["a", "b", "c"]
// for item in items:
// print(item)
// items still usable (different reason - ref counting)---
Unique Rust Concepts
Concepts Other Languages Lack
1. Borrow Checker: No other mainstream language has compile-time borrow checking 2. Lifetimes: Explicit annotation of reference validity 3. Move by Default: Values move, not copy 4. No Null: Option<T> instead of null pointers 5. Affine Types: Values can be used at most once
Learning Curve Areas
| Concept | Coming From | Key Insight |
|---|---|---|
| Ownership | GC languages | Think about who "owns" data |
| Borrowing | C/C++ | Like references but checked |
| Lifetimes | Any | Explicit scope of validity |
| Move | C++ | Move is default, not copy |
---
Mental Model Shifts
From GC Languages (Java, Go, Python)
Before: "Memory just works, GC handles it"
After: "I explicitly decide who owns data and when it's freed"Key shifts:
- Think about ownership at design time
- Returning references requires lifetime thinking
- No more
null- useOption<T>
From C/C++
Before: "I manually manage memory and hope I get it right"
After: "Compiler enforces correctness, I fight the borrow checker"Key shifts:
- Trust the compiler's errors
- Move is the default (unlike C++ copy)
- Smart pointers are idiomatic, not overhead
From Functional Languages (Haskell, ML)
Before: "Everything is immutable, copying is fine"
After: "Mutability is explicit, ownership prevents aliasing"Key shifts:
- Mutability is safe because of ownership rules
- No persistent data structures needed (usually)
- Performance characteristics are explicit
---
Performance Trade-offs
| Language | Memory Overhead | Latency | Throughput |
|---|---|---|---|
| Rust | Minimal (no GC) | Predictable | Excellent |
| C++ | Minimal | Predictable | Excellent |
| Go | GC overhead | GC pauses | Good |
| Java | GC overhead | GC pauses | Good |
| Python | High (ref counting + GC) | Variable | Lower |
When Rust Ownership Wins
1. Real-time systems: No GC pauses 2. Embedded: No runtime overhead 3. High-performance: Zero-cost abstractions 4. Concurrent: Data races prevented at compile time
When GC Might Be Preferable
1. Rapid prototyping: Less mental overhead 2. Complex object graphs: Cycles are tricky in Rust 3. GUI applications: Object lifetimes are dynamic 4. Small programs: Overhead doesn't matter
Ownership Best Practices
API Design Patterns
1. Prefer Borrowing Over Ownership
// BAD: takes ownership unnecessarily
fn print_name(name: String) {
println!("Name: {}", name);
}
// GOOD: borrows instead
fn print_name(name: &str) {
println!("Name: {}", name);
}
// Caller benefits:
let name = String::from("Alice");
print_name(&name); // can reuse name
print_name(&name); // still valid2. Return Owned Values from Constructors
// GOOD: return owned value
impl User {
fn new(name: &str) -> Self {
User {
name: name.to_string(),
}
}
}
// GOOD: accept Into<String> for flexibility
impl User {
fn new(name: impl Into<String>) -> Self {
User {
name: name.into(),
}
}
}
// Usage:
let u1 = User::new("Alice"); // &str
let u2 = User::new(String::from("Bob")); // String3. Use AsRef for Generic Borrowing
// GOOD: accepts both &str and String
fn process<S: AsRef<str>>(input: S) {
let s = input.as_ref();
println!("{}", s);
}
process("literal"); // &str
process(String::from("owned")); // String
process(&String::from("ref")); // &String4. Cow for Clone-on-Write
use std::borrow::Cow;
// Return borrowed when possible, owned when needed
fn maybe_modify(s: &str, uppercase: bool) -> Cow<'_, str> {
if uppercase {
Cow::Owned(s.to_uppercase()) // allocates
} else {
Cow::Borrowed(s) // zero-cost
}
}
let input = "hello";
let result = maybe_modify(input, false);
// result is borrowed, no allocation---
Struct Design Patterns
1. Owned Fields vs References
// Use owned fields for most cases
struct User {
name: String,
email: String,
}
// Use references only when lifetime is clear
struct UserView<'a> {
name: &'a str,
email: &'a str,
}
// Pattern: owned data + view for efficiency
impl User {
fn view(&self) -> UserView<'_> {
UserView {
name: &self.name,
email: &self.email,
}
}
}2. Builder Pattern with Ownership
#[derive(Default)]
struct RequestBuilder {
url: Option<String>,
method: Option<String>,
body: Option<Vec<u8>>,
}
impl RequestBuilder {
fn new() -> Self {
Self::default()
}
// Take self by value for chaining
fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
fn method(mut self, method: impl Into<String>) -> Self {
self.method = Some(method.into());
self
}
fn build(self) -> Result<Request, Error> {
Ok(Request {
url: self.url.ok_or(Error::MissingUrl)?,
method: self.method.unwrap_or_else(|| "GET".to_string()),
body: self.body.unwrap_or_default(),
})
}
}
// Usage:
let req = RequestBuilder::new()
.url("https://example.com")
.method("POST")
.build()?;3. Interior Mutability When Needed
use std::cell::RefCell;
use std::rc::Rc;
// Shared mutable state in single-threaded context
struct Counter {
value: Rc<RefCell<u32>>,
}
impl Counter {
fn new() -> Self {
Counter {
value: Rc::new(RefCell::new(0)),
}
}
fn increment(&self) {
*self.value.borrow_mut() += 1;
}
fn get(&self) -> u32 {
*self.value.borrow()
}
fn clone_handle(&self) -> Self {
Counter {
value: Rc::clone(&self.value),
}
}
}---
Collection Patterns
1. Efficient Iteration
let items = vec![1, 2, 3, 4, 5];
// Iterate by reference (no move)
for item in &items {
println!("{}", item);
}
// Iterate by mutable reference
for item in &mut items.clone() {
*item *= 2;
}
// Consume with into_iter when done
let sum: i32 = items.into_iter().sum();2. Collecting Results
// Collect into owned collection
let strings: Vec<String> = (0..5)
.map(|i| format!("item_{}", i))
.collect();
// Collect references
let refs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
// Collect with transformation
let result: Result<Vec<i32>, _> = ["1", "2", "3"]
.iter()
.map(|s| s.parse::<i32>())
.collect();3. Entry API for Maps
use std::collections::HashMap;
let mut map: HashMap<String, Vec<i32>> = HashMap::new();
// Efficient: don't search twice
map.entry("key".to_string())
.or_insert_with(Vec::new)
.push(42);
// With entry modification
map.entry("key".to_string())
.and_modify(|v| v.push(43))
.or_insert_with(|| vec![43]);---
Error Handling with Ownership
1. Preserve Context in Errors
use std::error::Error;
use std::fmt;
#[derive(Debug)]
struct ParseError {
input: String, // owns the problematic input
message: String,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Failed to parse '{}': {}", self.input, self.message)
}
}
fn parse(input: &str) -> Result<i32, ParseError> {
input.parse().map_err(|_| ParseError {
input: input.to_string(), // clone for error context
message: "not a valid integer".to_string(),
})
}2. Ownership in Result Chains
fn process_data(path: &str) -> Result<ProcessedData, Error> {
let content = std::fs::read_to_string(path)?; // owned String
let parsed = parse_content(&content)?; // borrow
let processed = transform(parsed)?; // ownership moves
Ok(processed) // return owned
}---
Performance Considerations
1. Avoid Unnecessary Clones
// BAD: cloning just to compare
fn contains_item(items: &[String], target: &str) -> bool {
items.iter().any(|s| s.clone() == target) // unnecessary clone
}
// GOOD: compare references
fn contains_item(items: &[String], target: &str) -> bool {
items.iter().any(|s| s == target) // String implements PartialEq<str>
}2. Use Slices for Flexibility
// BAD: requires Vec
fn sum(numbers: &Vec<i32>) -> i32 {
numbers.iter().sum()
}
// GOOD: accepts any slice
fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
// Now works with:
sum(&vec![1, 2, 3]); // Vec
sum(&[1, 2, 3]); // array
sum(&array[1..3]); // slice3. In-Place Mutation
// BAD: allocates new String
fn make_uppercase(s: &str) -> String {
s.to_uppercase()
}
// GOOD when you own the data: mutate in place
fn make_uppercase(mut s: String) -> String {
s.make_ascii_uppercase(); // in-place for ASCII
s
}Common Ownership Errors & Fixes
E0382: Use of Moved Value
Error Pattern
let s = String::from("hello");
let s2 = s; // s moved here
println!("{}", s); // ERROR: value borrowed after moveFix Options
Option 1: Clone (if ownership not needed)
let s = String::from("hello");
let s2 = s.clone(); // s is cloned
println!("{}", s); // OK: s still validOption 2: Borrow (if modification not needed)
let s = String::from("hello");
let s2 = &s; // borrow, not move
println!("{}", s); // OK
println!("{}", s2); // OKOption 3: Use Rc/Arc (for shared ownership)
use std::rc::Rc;
let s = Rc::new(String::from("hello"));
let s2 = Rc::clone(&s); // shared ownership
println!("{}", s); // OK
println!("{}", s2); // OK---
E0597: Borrowed Value Does Not Live Long Enough
Error Pattern
fn get_str() -> &str {
let s = String::from("hello");
&s // ERROR: s dropped here, but reference returned
}Fix Options
Option 1: Return owned value
fn get_str() -> String {
String::from("hello") // return owned value
}Option 2: Use 'static lifetime
fn get_str() -> &'static str {
"hello" // string literal has 'static lifetime
}Option 3: Accept reference parameter
fn get_str<'a>(s: &'a str) -> &'a str {
s // return reference with same lifetime as input
}---
E0499: Cannot Borrow as Mutable More Than Once
Error Pattern
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // ERROR: second mutable borrow
println!("{}, {}", r1, r2);Fix Options
Option 1: Sequential borrows
let mut s = String::from("hello");
{
let r1 = &mut s;
r1.push_str(" world");
} // r1 goes out of scope
let r2 = &mut s; // OK: r1 no longer existsOption 2: Use RefCell for interior mutability
use std::cell::RefCell;
let s = RefCell::new(String::from("hello"));
let mut r1 = s.borrow_mut();
// drop r1 before borrowing again
drop(r1);
let mut r2 = s.borrow_mut();---
E0502: Cannot Borrow as Mutable While Immutable Borrow Exists
Error Pattern
let mut v = vec![1, 2, 3];
let first = &v[0]; // immutable borrow
v.push(4); // ERROR: mutable borrow while immutable exists
println!("{}", first);Fix Options
Option 1: Finish using immutable borrow first
let mut v = vec![1, 2, 3];
let first = v[0]; // copy value, not borrow
v.push(4); // OK
println!("{}", first); // OK: using copied valueOption 2: Clone before mutating
let mut v = vec![1, 2, 3];
let first = v[0].clone(); // if T: Clone
v.push(4);
println!("{}", first);---
E0507: Cannot Move Out of Borrowed Content
Error Pattern
fn take_string(s: &String) {
let moved = *s; // ERROR: cannot move out of borrowed content
}Fix Options
Option 1: Clone
fn take_string(s: &String) {
let cloned = s.clone();
}Option 2: Take ownership in function signature
fn take_string(s: String) { // take ownership
let moved = s;
}Option 3: Use mem::take for Option/Default types
fn take_from_option(opt: &mut Option<String>) -> Option<String> {
std::mem::take(opt) // replaces with None, returns owned value
}---
E0515: Return Local Reference
Error Pattern
fn create_string() -> &String {
let s = String::from("hello");
&s // ERROR: cannot return reference to local variable
}Fix Options
Option 1: Return owned value
fn create_string() -> String {
String::from("hello")
}Option 2: Use static/const
fn get_static_str() -> &'static str {
"hello"
}---
E0716: Temporary Value Dropped While Borrowed
Error Pattern
let r: &str = &String::from("hello"); // ERROR: temporary dropped
println!("{}", r);Fix Options
Option 1: Bind to variable first
let s = String::from("hello");
let r: &str = &s;
println!("{}", r);Option 2: Use let binding with reference
let r: &str = {
let s = String::from("hello");
// s.as_str() // ERROR: still temporary
Box::leak(s.into_boxed_str()) // extreme: leak for 'static
};---
Pattern: Loop Ownership Issues
Error Pattern
let strings = vec![String::from("a"), String::from("b")];
for s in strings {
println!("{}", s);
}
// ERROR: strings moved into loop
println!("{:?}", strings);Fix Options
Option 1: Iterate by reference
let strings = vec![String::from("a"), String::from("b")];
for s in &strings {
println!("{}", s);
}
println!("{:?}", strings); // OKOption 2: Use iter()
for s in strings.iter() {
println!("{}", s);
}Option 3: Clone if needed
for s in strings.clone() {
// consumes cloned vec
}
println!("{:?}", strings); // original still availableLifetime Patterns
Basic Lifetime Annotation
When Required
// ERROR: missing lifetime specifier
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
// FIX: explicit lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}Lifetime Elision Rules
1. Each input reference gets its own lifetime 2. If one input lifetime, output uses same 3. If &self or &mut self, output uses self's lifetime
// These are equivalent (elision applies):
fn first_word(s: &str) -> &str { ... }
fn first_word<'a>(s: &'a str) -> &'a str { ... }
// Method with self (elision applies):
impl MyStruct {
fn get_ref(&self) -> &str { ... }
// Equivalent to:
fn get_ref<'a>(&'a self) -> &'a str { ... }
}---
Struct Lifetimes
Struct Holding References
// Struct must declare lifetime for references
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 { 3 }
// Return reference tied to self's lifetime
fn get_part(&self) -> &str {
self.part
}
}Multiple Lifetimes in Struct
struct Multi<'a, 'b> {
x: &'a str,
y: &'b str,
}
// Use when references may have different lifetimes
fn make_multi<'a, 'b>(x: &'a str, y: &'b str) -> Multi<'a, 'b> {
Multi { x, y }
}---
'static Lifetime
When to Use
// String literals are 'static
let s: &'static str = "hello";
// Owned data can be leaked to 'static
let leaked: &'static str = Box::leak(String::from("hello").into_boxed_str());
// Thread spawn requires 'static or move
std::thread::spawn(move || {
// closure owns data, satisfies 'static
});Avoid Overusing 'static
// BAD: requires 'static unnecessarily
fn process(s: &'static str) { ... }
// GOOD: use generic lifetime
fn process<'a>(s: &'a str) { ... }
// or
fn process(s: &str) { ... } // lifetime elision---
Higher-Ranked Trait Bounds (HRTB)
for<'a> Syntax
// Function that works with any lifetime
fn apply_to_ref<F>(f: F)
where
F: for<'a> Fn(&'a str) -> &'a str,
{
let s = String::from("hello");
let result = f(&s);
println!("{}", result);
}Common Use: Closure Bounds
// Closure that borrows any lifetime
fn filter_refs<F>(items: &[&str], pred: F) -> Vec<&str>
where
F: for<'a> Fn(&'a str) -> bool,
{
items.iter().copied().filter(|s| pred(s)).collect()
}---
Lifetime Bounds
'a: 'b (Outlives)
// 'a must live at least as long as 'b
fn coerce<'a, 'b>(x: &'a str) -> &'b str
where
'a: 'b,
{
x
}T: 'a (Type Outlives Lifetime)
// T must live at least as long as 'a
struct Wrapper<'a, T: 'a> {
value: &'a T,
}
// Common pattern with trait objects
fn use_trait<'a, T: MyTrait + 'a>(t: &'a T) { ... }---
Common Lifetime Mistakes
Mistake 1: Returning Reference to Local
// WRONG
fn dangle() -> &String {
let s = String::from("hello");
&s // s dropped, reference invalid
}
// RIGHT
fn no_dangle() -> String {
String::from("hello")
}Mistake 2: Conflicting Lifetimes
// WRONG: might return reference to y which has shorter lifetime
fn wrong<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
y // ERROR: 'b might not live as long as 'a
}
// RIGHT: use same lifetime or add bound
fn right<'a>(x: &'a str, y: &'a str) -> &'a str {
y // OK: both have lifetime 'a
}Mistake 3: Struct Outlives Reference
// WRONG: s might outlive the string it references
let r;
{
let s = String::from("hello");
r = Excerpt { part: &s }; // ERROR
}
println!("{}", r.part); // s already dropped
// RIGHT: ensure source outlives struct
let s = String::from("hello");
let r = Excerpt { part: &s };
println!("{}", r.part); // OK: s still in scope---
Subtyping and Variance
Covariance
// &'a T is covariant in 'a
// Can use &'long where &'short expected
fn example<'short, 'long: 'short>(long_ref: &'long str) {
let short_ref: &'short str = long_ref; // OK: covariance
}Invariance
// &'a mut T is invariant in 'a
fn example<'a, 'b>(x: &'a mut &'b str, y: &'b str) {
*x = y; // ERROR if 'a and 'b are different
}Practical Impact
// This works due to covariance
fn accept_any<'a>(s: &'a str) { ... }
let s = String::from("hello");
let long_lived: &str = &s;
accept_any(long_lived); // 'long coerces to 'shortRelated skills
FAQ
How is Rust move semantics different from C++?
m01-ownership shows that after `let b = a` in Rust, `a` is invalid at compile time, while C++ `std::move(a)` leaves `a` in a valid but unspecified state that can compile yet behave incorrectly.
Who should use the m01-ownership skill?
m01-ownership suits developers learning Rust ownership by comparing it with C++ and Go, especially when borrow checker errors appear while writing concurrent or systems Rust code.
Is M01 Ownership safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.