docs: ownership and lifetime patterns from rust-lang/rust
10 patterns, 677 lines. Full spec compliance. Patterns: borrowing over owning, Clone/Copy, Cow, mem::take, Box, Arc, Drop/RAII, lifetime elision, AsRef, PhantomData.
This commit is contained in:
@@ -0,0 +1,677 @@
|
||||
# Rust Ownership & Lifetime Patterns
|
||||
|
||||
Patterns for ownership, borrowing, and lifetimes in Rust, extracted
|
||||
from the standard library source.
|
||||
|
||||
**Source:** [rust-lang/rust](https://github.com/rust-lang/rust) at commit
|
||||
[`f53b654`](https://github.com/rust-lang/rust/tree/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6)
|
||||
|
||||
**Stats:** 7,909 &mut references, 798 Rc/Arc, 697 Box<>, 410 Cell/RefCell,
|
||||
197 Cow<>, 254 Drop impls, 401 PhantomData, 112 mem::take/replace.
|
||||
|
||||
---
|
||||
|
||||
## 1. Borrowing Over Owning in Parameters
|
||||
|
||||
### Source:
|
||||
|
||||
[library/alloc/src/string.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/alloc/src/string.rs) (String/str relationship)
|
||||
|
||||
```rust
|
||||
// The stdlib accepts &str not String for read-only access:
|
||||
impl str {
|
||||
pub fn contains<P: Pattern>(&self, pat: P) -> bool { ... }
|
||||
pub fn starts_with<P: Pattern>(&self, pat: P) -> bool { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
Taking `&T` instead of `T` means the caller keeps ownership. This
|
||||
is the most fundamental Rust pattern: borrow when you only need to
|
||||
read, own when you need to keep or modify.
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- You only need to read the value (don't need to store it)
|
||||
- You want to accept both owned and borrowed values (String and &str)
|
||||
- You don't want to force callers to clone
|
||||
|
||||
**Example — before:**
|
||||
```rust
|
||||
// Takes ownership unnecessarily — forces caller to clone
|
||||
fn greet(name: String) {
|
||||
println!("Hello, {name}!");
|
||||
}
|
||||
|
||||
let name = String::from("Alice");
|
||||
greet(name); // name is moved — can't use it anymore
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```rust
|
||||
// Borrows — caller keeps ownership
|
||||
fn greet(name: &str) {
|
||||
println!("Hello, {name}!");
|
||||
}
|
||||
|
||||
let name = String::from("Alice");
|
||||
greet(&name); // works with &String (deref coercion)
|
||||
greet("Bob"); // works with &str directly
|
||||
// name is still usable here
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- You need to store the value in a struct (take ownership)
|
||||
- You need to send it to another thread (ownership needed for Send)
|
||||
- The function will outlive the caller (return a Future that needs 'static)
|
||||
|
||||
---
|
||||
|
||||
## 2. Clone vs Copy Semantics
|
||||
|
||||
### Source:
|
||||
|
||||
[library/core/src/clone.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/clone.rs), [library/core/src/marker.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/marker.rs) (Copy)
|
||||
|
||||
Top derives: Clone (880), Copy (537).
|
||||
|
||||
```rust
|
||||
// Copy = implicit bitwise copy (stack only, no heap)
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Duration { secs: u64, nanos: Nanoseconds }
|
||||
|
||||
// Clone only = explicit .clone() needed (may allocate)
|
||||
#[derive(Clone)]
|
||||
pub struct String { vec: Vec<u8> } // Can't be Copy — heap allocated
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
Copy types are implicitly duplicated on assignment (like integers).
|
||||
Clone types require explicit `.clone()`. The distinction prevents
|
||||
accidental expensive copies. If you can be Copy, you should be.
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- **Copy:** Type is small, stack-only, bitwise-copyable (no heap,
|
||||
no Drop impl, no &mut interior)
|
||||
- **Clone only:** Type allocates, has Drop, or copying is expensive
|
||||
|
||||
**Example — before:**
|
||||
```rust
|
||||
// Missing Copy on a small stack type
|
||||
struct Point { x: f64, y: f64 }
|
||||
|
||||
let p = Point { x: 1.0, y: 2.0 };
|
||||
let q = p; // MOVED — p is now invalid
|
||||
// println!("{}", p.x); // ERROR: use after move
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```rust
|
||||
#[derive(Clone, Copy)]
|
||||
struct Point { x: f64, y: f64 }
|
||||
|
||||
let p = Point { x: 1.0, y: 2.0 };
|
||||
let q = p; // COPIED — p is still valid
|
||||
println!("{}", p.x); // Works fine
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- Type has heap allocation (Vec, String, Box) — can't be Copy
|
||||
- Type has a Drop impl — can't be Copy
|
||||
- Implicit copying would be expensive or surprising
|
||||
- Adding Copy now would be a breaking change to remove later
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```rust
|
||||
// DON'T: Derive Copy on something that might grow
|
||||
#[derive(Clone, Copy)]
|
||||
struct Config {
|
||||
port: u16,
|
||||
// Later you add: name: String — BREAKS because String isn't Copy
|
||||
}
|
||||
|
||||
// DO: Only derive Copy on types that are permanently small and stack-only
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Cow<'a, B> (Clone on Write)
|
||||
|
||||
### Source:
|
||||
|
||||
[library/alloc/src/borrow.rs#L169](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/alloc/src/borrow.rs#L169)
|
||||
|
||||
```rust
|
||||
pub enum Cow<'a, B: ?Sized + 'a> where B: ToOwned {
|
||||
Borrowed(&'a B),
|
||||
Owned(<B as ToOwned>::Owned),
|
||||
}
|
||||
```
|
||||
|
||||
197 usages in the stdlib.
|
||||
|
||||
### Why
|
||||
|
||||
Cow delays cloning until mutation is needed. If you only read, you
|
||||
pay zero allocation cost (borrowed). If you need to modify, it
|
||||
clones on first write. This is the "avoid allocation in the common
|
||||
case" pattern.
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- A function usually returns borrowed data but sometimes needs to
|
||||
allocate (e.g., string escaping — most strings need no change)
|
||||
- You want to accept either owned or borrowed without forcing a clone
|
||||
- Performance matters and most inputs don't need modification
|
||||
|
||||
**Example — before:**
|
||||
```rust
|
||||
// Always allocates even when input needs no change
|
||||
fn escape(s: &str) -> String {
|
||||
if s.contains('<') {
|
||||
s.replace('<', "<") // allocates
|
||||
} else {
|
||||
s.to_string() // ALSO allocates — unnecessary!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```rust
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn escape(s: &str) -> Cow<'_, str> {
|
||||
if s.contains('<') {
|
||||
Cow::Owned(s.replace('<', "<")) // allocates only when needed
|
||||
} else {
|
||||
Cow::Borrowed(s) // zero-cost — just returns a reference
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- You always modify the value (just return the owned type)
|
||||
- The lifetime makes the API confusing for callers
|
||||
- The performance difference is negligible
|
||||
|
||||
---
|
||||
|
||||
## 4. mem::take / mem::replace (Move Out of &mut)
|
||||
|
||||
### Source:
|
||||
|
||||
[library/core/src/mem/mod.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/mem/mod.rs)
|
||||
|
||||
112 usages of `mem::take`/`mem::replace` in the stdlib.
|
||||
|
||||
```rust
|
||||
pub fn take<T: Default>(dest: &mut T) -> T {
|
||||
replace(dest, T::default())
|
||||
}
|
||||
|
||||
pub fn replace<T>(dest: &mut T, src: T) -> T {
|
||||
// Swaps src into dest, returns old dest
|
||||
unsafe { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
You can't move out of a `&mut T` directly (would leave the reference
|
||||
dangling). `mem::take` solves this by replacing with Default and
|
||||
giving you the old value. This is the idiomatic way to "take
|
||||
ownership through a mutable reference."
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- You have `&mut self` and need to move a field out
|
||||
- You're implementing state machines (swap states)
|
||||
- You need to "reset" a field while extracting its value
|
||||
|
||||
**Example — before:**
|
||||
```rust
|
||||
struct Parser {
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn flush(&mut self) -> Vec<u8> {
|
||||
let result = self.buffer.clone(); // EXPENSIVE clone
|
||||
self.buffer.clear();
|
||||
result
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```rust
|
||||
use std::mem;
|
||||
|
||||
impl Parser {
|
||||
fn flush(&mut self) -> Vec<u8> {
|
||||
mem::take(&mut self.buffer) // moves buffer out, replaces with empty Vec
|
||||
// Zero allocations, zero copies
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- The type doesn't implement Default
|
||||
- You need to keep the original value (use clone)
|
||||
- You're in a consuming method (just take self)
|
||||
|
||||
---
|
||||
|
||||
## 5. Box<T> for Heap Allocation
|
||||
|
||||
### Source:
|
||||
|
||||
[library/alloc/src/boxed.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/alloc/src/boxed.rs)
|
||||
|
||||
697 Box<> usages in library/.
|
||||
|
||||
### Why
|
||||
|
||||
Box is the simplest smart pointer: single-owner heap allocation.
|
||||
Use it when you need a value on the heap with known, fixed ownership.
|
||||
Recursive types REQUIRE boxing (otherwise infinite size).
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- Recursive data structures (tree nodes, linked lists)
|
||||
- Trait objects (`Box<dyn Error>`, `Box<dyn Fn()>`)
|
||||
- Large values you want on the heap to avoid stack overflow
|
||||
- Transferring ownership without copying large structs
|
||||
|
||||
**Example — before:**
|
||||
```rust
|
||||
// COMPILE ERROR: recursive type has infinite size
|
||||
enum Tree {
|
||||
Leaf(i32),
|
||||
Node(Tree, Tree), // How big is Tree? Depends on Tree... infinite
|
||||
}
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```rust
|
||||
enum Tree {
|
||||
Leaf(i32),
|
||||
Node(Box<Tree>, Box<Tree>), // Box is pointer-sized — finite!
|
||||
}
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- The value is small and stack allocation is fine
|
||||
- You need shared ownership (use Rc/Arc)
|
||||
- You need interior mutability (use RefCell or Mutex)
|
||||
- You're boxing just to avoid lifetime annotations (fix the lifetimes)
|
||||
|
||||
---
|
||||
|
||||
## 6. Arc<T> for Shared Ownership Across Threads
|
||||
|
||||
### Source:
|
||||
|
||||
[library/alloc/src/sync.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/alloc/src/sync.rs)
|
||||
|
||||
798 Rc/Arc usages in library/.
|
||||
|
||||
### Why
|
||||
|
||||
Arc (Atomic Reference Counted) enables multiple owners across threads.
|
||||
The data lives on the heap; cloning an Arc increments the reference
|
||||
count (not the data). Data is freed when the last Arc is dropped.
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- Multiple threads need read access to the same data
|
||||
- Ownership is shared (no single owner)
|
||||
- Combined with Mutex for shared mutable state: `Arc<Mutex<T>>`
|
||||
|
||||
**Example — before:**
|
||||
```rust
|
||||
// Can't send &data to another thread — lifetime issue
|
||||
fn spawn_with_data(data: &Config) {
|
||||
std::thread::spawn(|| {
|
||||
// ERROR: borrowed data may not outlive the scope
|
||||
println!("{}", data.name);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
|
||||
fn spawn_with_data(data: Arc<Config>) {
|
||||
let data = Arc::clone(&data); // cheap: just increments counter
|
||||
std::thread::spawn(move || {
|
||||
println!("{}", data.name); // owns a reference, lives as long as needed
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- Single-threaded (use Rc — no atomic overhead)
|
||||
- Single owner is sufficient (use Box)
|
||||
- You can restructure to avoid shared ownership (prefer this)
|
||||
- You're using it because you can't figure out lifetimes (usually
|
||||
a design smell)
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```rust
|
||||
// DON'T: Arc everything because lifetimes are hard
|
||||
struct App {
|
||||
db: Arc<Database>,
|
||||
cache: Arc<Cache>,
|
||||
config: Arc<Config>,
|
||||
logger: Arc<Logger>,
|
||||
}
|
||||
// If App owns all of these, just use owned fields!
|
||||
// Arc is for SHARING, not for avoiding lifetime annotations.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Drop for Cleanup (RAII)
|
||||
|
||||
### Source:
|
||||
|
||||
[library/core/src/ops/drop.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/ops/drop.rs)
|
||||
|
||||
254 Drop implementations in the stdlib.
|
||||
|
||||
```rust
|
||||
pub trait Drop {
|
||||
fn drop(&mut self);
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
Drop runs automatically when a value goes out of scope. This is RAII:
|
||||
resources are tied to ownership. File handles close, locks release,
|
||||
memory frees — all automatically, without try/finally or defer.
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- Your type holds an external resource (file, socket, lock)
|
||||
- You need cleanup code to run even on panic
|
||||
- You're implementing a smart pointer or wrapper
|
||||
|
||||
**Example — before (in another language):**
|
||||
```python
|
||||
# Must remember to close — easy to forget on error paths
|
||||
f = open("data.txt")
|
||||
try:
|
||||
process(f)
|
||||
finally:
|
||||
f.close() # What if we forget this?
|
||||
```
|
||||
|
||||
**Example — in Rust:**
|
||||
```rust
|
||||
// Drop handles cleanup automatically
|
||||
{
|
||||
let f = File::open("data.txt")?;
|
||||
process(&f)?;
|
||||
} // f.drop() called here — file closed automatically
|
||||
// Even if process() panics, Drop still runs during unwinding
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- Type doesn't hold external resources (just let memory free)
|
||||
- You need Copy (can't impl both Drop and Copy)
|
||||
- Cleanup order matters precisely (Drop order is reverse of creation,
|
||||
but this isn't always what you want — use explicit close methods)
|
||||
|
||||
---
|
||||
|
||||
## 8. Lifetime Elision (Let the Compiler Infer)
|
||||
|
||||
### Source:
|
||||
|
||||
The Rust compiler applies 3 elision rules automatically. The stdlib
|
||||
exploits this — most function signatures DON'T write lifetimes:
|
||||
|
||||
```rust
|
||||
// Written in source (elided):
|
||||
fn first_word(s: &str) -> &str { ... }
|
||||
|
||||
// What the compiler actually sees:
|
||||
fn first_word<'a>(s: &'a str) -> &'a str { ... }
|
||||
```
|
||||
|
||||
1,206 explicit lifetime annotations exist in library/ — only
|
||||
where elision rules don't apply.
|
||||
|
||||
### Why
|
||||
|
||||
Lifetimes are inferred in the common case. Only annotate when:
|
||||
- Multiple references in input (compiler can't guess which one
|
||||
the output borrows from)
|
||||
- Struct fields that borrow
|
||||
- Static or complex relationships
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- Function has one reference parameter and returns a reference
|
||||
(rule 1: output gets input's lifetime)
|
||||
- Method has `&self` and returns a reference
|
||||
(rule 3: output gets self's lifetime)
|
||||
|
||||
**Example — before:**
|
||||
```rust
|
||||
// Unnecessary explicit lifetimes
|
||||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
if x.len() > y.len() { x } else { y }
|
||||
}
|
||||
// This NEEDS annotations because there are TWO input references
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```rust
|
||||
// Let elision work when it can:
|
||||
fn trim(s: &str) -> &str { s.trim() }
|
||||
// No annotations needed — one input ref, one output ref → same lifetime
|
||||
|
||||
// Only annotate when the compiler ASKS you to
|
||||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- Multiple input references exist (compiler can't infer which)
|
||||
- Struct borrows from multiple sources
|
||||
- The relationship is non-obvious (annotate for clarity even
|
||||
if the compiler could infer it)
|
||||
|
||||
---
|
||||
|
||||
## 9. AsRef/Into for Flexible Function Parameters
|
||||
|
||||
### Source:
|
||||
|
||||
[library/core/src/convert/mod.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/convert/mod.rs)
|
||||
|
||||
79 AsRef implementations in library/.
|
||||
|
||||
```rust
|
||||
// std::fs::read accepts anything that can become a Path:
|
||||
pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> { ... }
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
`AsRef<Path>` accepts `&str`, `String`, `PathBuf`, `&Path` — any
|
||||
type that cheaply references a Path. One function, many input types,
|
||||
zero allocation.
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- You want to accept multiple types that can cheaply become &T
|
||||
- Ergonomics: let callers pass String or &str interchangeably
|
||||
- The conversion is cheap (reference, not allocation)
|
||||
|
||||
**Example — before:**
|
||||
```rust
|
||||
// Accepts only &Path — callers must convert manually
|
||||
fn file_exists(path: &Path) -> bool {
|
||||
path.exists()
|
||||
}
|
||||
|
||||
file_exists(Path::new("/tmp/foo")); // works
|
||||
// file_exists("/tmp/foo"); // ERROR — &str isn't &Path
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```rust
|
||||
fn file_exists(path: impl AsRef<Path>) -> bool {
|
||||
path.as_ref().exists()
|
||||
}
|
||||
|
||||
file_exists("/tmp/foo"); // &str works
|
||||
file_exists(String::from("/tmp")); // String works
|
||||
file_exists(PathBuf::from("/tmp")); // PathBuf works
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- The function is private/internal (just accept the concrete type)
|
||||
- The generic bound adds confusion for no real benefit
|
||||
- You need ownership, not a reference (use `Into<T>` instead)
|
||||
|
||||
---
|
||||
|
||||
## 10. PhantomData for Unused Type Parameters
|
||||
|
||||
### Source:
|
||||
|
||||
[library/core/src/marker.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/marker.rs) (PhantomData)
|
||||
|
||||
401 PhantomData usages in the stdlib.
|
||||
|
||||
```rust
|
||||
pub struct PhantomData<T: ?Sized>;
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
Sometimes you need a type parameter for lifetime or type safety but
|
||||
don't actually store the value. PhantomData tells the compiler "I'm
|
||||
logically related to T" without physically containing T. Used for
|
||||
variance, drop checking, and marker relationships.
|
||||
|
||||
### When to Use
|
||||
|
||||
**Triggers:**
|
||||
- Your struct has a type parameter it doesn't store (for type safety)
|
||||
- You need correct variance (covariant, contravariant, invariant)
|
||||
- You need the compiler to know your type "owns" a T for drop checking
|
||||
- Implementing raw pointer wrappers that should act like &T
|
||||
|
||||
**Example — before:**
|
||||
```rust
|
||||
// COMPILE ERROR: parameter T is never used
|
||||
struct TypedId<T> {
|
||||
id: u64,
|
||||
}
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```rust
|
||||
use std::marker::PhantomData;
|
||||
|
||||
struct TypedId<T> {
|
||||
id: u64,
|
||||
_phantom: PhantomData<T>, // tells compiler this is related to T
|
||||
}
|
||||
|
||||
// Now TypedId<User> and TypedId<Product> are different types!
|
||||
let user_id: TypedId<User> = TypedId { id: 42, _phantom: PhantomData };
|
||||
let product_id: TypedId<Product> = TypedId { id: 42, _phantom: PhantomData };
|
||||
// user_id == product_id // COMPILE ERROR: different types
|
||||
```
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
**Don't use this when:**
|
||||
- You can just store the T directly
|
||||
- The type parameter doesn't add safety (just remove it)
|
||||
- You're cargo-culting PhantomData without understanding variance
|
||||
|
||||
---
|
||||
|
||||
## Summary: Ownership Decision Tree
|
||||
|
||||
```
|
||||
Who needs this value?
|
||||
├── One owner, stack → plain value (no wrapper)
|
||||
├── One owner, heap → Box<T>
|
||||
├── Multiple owners, single thread → Rc<T>
|
||||
├── Multiple owners, multi thread → Arc<T>
|
||||
└── Need mutation with shared refs?
|
||||
├── Single thread → RefCell<T> (runtime borrow check)
|
||||
└── Multi thread → Mutex<T> or RwLock<T>
|
||||
|
||||
How to pass to a function?
|
||||
├── Read only → &T (borrow)
|
||||
├── Need to modify → &mut T (mutable borrow)
|
||||
├── Need to store/send → T (ownership transfer)
|
||||
├── Accept multiple types → impl AsRef<T> or impl Into<T>
|
||||
└── Might need to clone → Cow<'a, T>
|
||||
|
||||
Need to move out of &mut?
|
||||
└── mem::take (replaces with Default, returns old value)
|
||||
```
|
||||
|
||||
| Pattern | Use when |
|
||||
|---|---|
|
||||
| `&T` parameter | Read-only access, no ownership needed |
|
||||
| `T` parameter | Must store, send, or consume the value |
|
||||
| `Clone`/`Copy` | Small stack types (Copy), expensive heap types (Clone) |
|
||||
| `Cow<'a, B>` | Usually borrowed, sometimes needs to own |
|
||||
| `mem::take` | Move a field out of &mut self |
|
||||
| `Box<T>` | Heap allocation with single owner |
|
||||
| `Arc<T>` | Shared ownership across threads |
|
||||
| `Drop` | RAII cleanup (files, locks, connections) |
|
||||
| Lifetime elision | Let compiler infer when rules apply |
|
||||
| `AsRef<T>` | Accept multiple types cheaply |
|
||||
| `PhantomData<T>` | Unused type parameter for type safety |
|
||||
|
||||
See also:
|
||||
- [traits.md](traits.md) — Clone, Copy, Drop trait design
|
||||
- [concurrency.md](concurrency.md) — Arc/Mutex patterns
|
||||
- [error-handling.md](error-handling.md) — Ownership in error propagation
|
||||
|
||||
<!-- PATTERN_COMPLETE -->
|
||||
Reference in New Issue
Block a user