agentic-team-templates 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +4 -0
- package/src/index.test.js +1 -0
- package/templates/rust-expert/.cursorrules/concurrency.md +250 -0
- package/templates/rust-expert/.cursorrules/ecosystem-and-tooling.md +299 -0
- package/templates/rust-expert/.cursorrules/error-handling.md +190 -0
- package/templates/rust-expert/.cursorrules/overview.md +142 -0
- package/templates/rust-expert/.cursorrules/ownership-and-borrowing.md +204 -0
- package/templates/rust-expert/.cursorrules/performance-and-unsafe.md +256 -0
- package/templates/rust-expert/.cursorrules/testing.md +300 -0
- package/templates/rust-expert/.cursorrules/traits-and-generics.md +236 -0
- package/templates/rust-expert/CLAUDE.md +283 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Rust Error Handling
|
|
2
|
+
|
|
3
|
+
Rust has no exceptions. `Result<T, E>` and `Option<T>` are the error handling primitives. The `?` operator propagates errors ergonomically, but every error path is still an explicit decision.
|
|
4
|
+
|
|
5
|
+
## Custom Error Types
|
|
6
|
+
|
|
7
|
+
### Using thiserror (Libraries)
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
use thiserror::Error;
|
|
11
|
+
|
|
12
|
+
#[derive(Debug, Error)]
|
|
13
|
+
pub enum AppError {
|
|
14
|
+
#[error("configuration error: {message}")]
|
|
15
|
+
Config { message: String },
|
|
16
|
+
|
|
17
|
+
#[error("database query failed")]
|
|
18
|
+
Database(#[from] sqlx::Error),
|
|
19
|
+
|
|
20
|
+
#[error("request to {url} failed")]
|
|
21
|
+
Http {
|
|
22
|
+
url: String,
|
|
23
|
+
#[source]
|
|
24
|
+
source: reqwest::Error,
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
#[error("record not found: {entity} with id {id}")]
|
|
28
|
+
NotFound { entity: &'static str, id: String },
|
|
29
|
+
|
|
30
|
+
#[error("validation failed: {0}")]
|
|
31
|
+
Validation(String),
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Using anyhow (Applications)
|
|
36
|
+
|
|
37
|
+
```rust
|
|
38
|
+
use anyhow::{Context, Result, bail, ensure};
|
|
39
|
+
|
|
40
|
+
fn load_config(path: &Path) -> Result<Config> {
|
|
41
|
+
let contents = fs::read_to_string(path)
|
|
42
|
+
.with_context(|| format!("reading config from {}", path.display()))?;
|
|
43
|
+
|
|
44
|
+
let config: Config = toml::from_str(&contents)
|
|
45
|
+
.context("parsing config TOML")?;
|
|
46
|
+
|
|
47
|
+
ensure!(!config.database_url.is_empty(), "database_url must not be empty");
|
|
48
|
+
|
|
49
|
+
if config.port == 0 {
|
|
50
|
+
bail!("port must be non-zero");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Ok(config)
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### When to Use Which
|
|
58
|
+
|
|
59
|
+
- **thiserror** — libraries, reusable crates, when callers need to match on error variants
|
|
60
|
+
- **anyhow** — applications, binaries, when you just need to propagate context up to main
|
|
61
|
+
- **Manual impl** — when you need full control or minimal dependencies
|
|
62
|
+
|
|
63
|
+
### Manual Error Implementation
|
|
64
|
+
|
|
65
|
+
```rust
|
|
66
|
+
#[derive(Debug)]
|
|
67
|
+
pub enum StorageError {
|
|
68
|
+
Io(std::io::Error),
|
|
69
|
+
Serialization(serde_json::Error),
|
|
70
|
+
NotFound { key: String },
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
impl fmt::Display for StorageError {
|
|
74
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
75
|
+
match self {
|
|
76
|
+
Self::Io(e) => write!(f, "storage I/O error: {e}"),
|
|
77
|
+
Self::Serialization(e) => write!(f, "serialization error: {e}"),
|
|
78
|
+
Self::NotFound { key } => write!(f, "key not found: {key}"),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
impl std::error::Error for StorageError {
|
|
84
|
+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
85
|
+
match self {
|
|
86
|
+
Self::Io(e) => Some(e),
|
|
87
|
+
Self::Serialization(e) => Some(e),
|
|
88
|
+
Self::NotFound { .. } => None,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
impl From<std::io::Error> for StorageError {
|
|
94
|
+
fn from(e: std::io::Error) -> Self {
|
|
95
|
+
Self::Io(e)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## The ? Operator
|
|
101
|
+
|
|
102
|
+
```rust
|
|
103
|
+
// ? is sugar for early return on Err/None
|
|
104
|
+
// It calls From::from() on the error, enabling automatic conversion
|
|
105
|
+
|
|
106
|
+
fn process(path: &Path) -> Result<Data, AppError> {
|
|
107
|
+
let raw = fs::read(path)?; // io::Error -> AppError via From
|
|
108
|
+
let parsed = serde_json::from_slice(&raw)?; // serde::Error -> AppError via From
|
|
109
|
+
Ok(parsed)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ? works with Option too
|
|
113
|
+
fn first_word(s: &str) -> Option<&str> {
|
|
114
|
+
let end = s.find(' ')?; // Returns None if no space
|
|
115
|
+
Some(&s[..end])
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Option Patterns
|
|
120
|
+
|
|
121
|
+
```rust
|
|
122
|
+
// Prefer combinators over match when they're clearer
|
|
123
|
+
let name = user.name.as_deref().unwrap_or("anonymous");
|
|
124
|
+
|
|
125
|
+
let parsed = input
|
|
126
|
+
.strip_prefix("v")
|
|
127
|
+
.and_then(|s| s.parse::<u32>().ok());
|
|
128
|
+
|
|
129
|
+
// Use match when you need complex logic per variant
|
|
130
|
+
match config.log_level {
|
|
131
|
+
Some(level) => logger.set_level(level),
|
|
132
|
+
None => logger.set_level(Level::Info),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Use if let for single-variant checks
|
|
136
|
+
if let Some(token) = headers.get("Authorization") {
|
|
137
|
+
authenticate(token)?;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Panic Policy
|
|
142
|
+
|
|
143
|
+
```rust
|
|
144
|
+
// Panics are for bugs, not for expected error conditions.
|
|
145
|
+
|
|
146
|
+
// Acceptable: debug assertions
|
|
147
|
+
debug_assert!(index < len, "index {index} out of bounds for length {len}");
|
|
148
|
+
|
|
149
|
+
// Acceptable: unreachable code paths that truly can't happen
|
|
150
|
+
match state {
|
|
151
|
+
State::Ready => process(),
|
|
152
|
+
State::Done => return,
|
|
153
|
+
// If we've exhaustively handled all variants and the compiler
|
|
154
|
+
// still wants a branch, use unreachable!() — but only when
|
|
155
|
+
// you can prove it's unreachable.
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Acceptable: unwrap() in tests
|
|
159
|
+
#[cfg(test)]
|
|
160
|
+
fn setup() -> TestDb {
|
|
161
|
+
TestDb::new().unwrap() // Fine in tests
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Never acceptable: unwrap() in library code without a VERY good reason
|
|
165
|
+
// Never acceptable: panic!() for user input validation
|
|
166
|
+
// Never acceptable: todo!() in shipped code
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Anti-Patterns
|
|
170
|
+
|
|
171
|
+
```rust
|
|
172
|
+
// Never: Silencing errors
|
|
173
|
+
let _ = write!(f, "data"); // The write might fail — handle it or explain why not
|
|
174
|
+
|
|
175
|
+
// Never: unwrap() with no context
|
|
176
|
+
let config = load_config().unwrap(); // What failed? Where? Why?
|
|
177
|
+
// At minimum:
|
|
178
|
+
let config = load_config().expect("loading config from default path");
|
|
179
|
+
|
|
180
|
+
// Never: Stringly-typed errors
|
|
181
|
+
fn process() -> Result<(), String> { // Don't use String as an error type
|
|
182
|
+
Err("something went wrong".into())
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Never: Catching panics as error handling
|
|
186
|
+
let result = std::panic::catch_unwind(|| dangerous_code()); // This is not error handling
|
|
187
|
+
|
|
188
|
+
// Never: .ok() to discard error information without justification
|
|
189
|
+
let maybe = fallible_operation().ok(); // Why is the error not important?
|
|
190
|
+
```
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Rust Expert
|
|
2
|
+
|
|
3
|
+
Guidelines for principal-level Rust engineering. Ownership, zero-cost abstractions, and fearless concurrency — wielded with precision.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This ruleset applies to:
|
|
8
|
+
- Systems programming and embedded targets
|
|
9
|
+
- Web services and async runtimes (Tokio, async-std)
|
|
10
|
+
- CLI tools and developer utilities
|
|
11
|
+
- Libraries and crates published to crates.io
|
|
12
|
+
- WebAssembly targets
|
|
13
|
+
- FFI and interop with C/C++
|
|
14
|
+
- Performance-critical applications
|
|
15
|
+
|
|
16
|
+
## Core Philosophy
|
|
17
|
+
|
|
18
|
+
Rust gives you power without sacrificing safety. The compiler is your closest collaborator — work with it, not against it.
|
|
19
|
+
|
|
20
|
+
- **If it compiles, it's probably correct.** The borrow checker isn't an obstacle — it's catching bugs that would have been CVEs in another language. Trust it.
|
|
21
|
+
- **Zero-cost abstractions are the point.** You should never have to choose between expressiveness and performance. If the abstraction has runtime cost, question whether it's the right abstraction.
|
|
22
|
+
- **Make illegal states unrepresentable.** Use the type system to encode invariants. If a state shouldn't exist, make it impossible to construct.
|
|
23
|
+
- **Explicit over implicit.** Lifetimes, ownership, error handling — Rust surfaces what other languages hide. This is a feature, not a flaw.
|
|
24
|
+
- **Unsafe is a scalpel, not a sledgehammer.** Every `unsafe` block is a proof obligation. If you can't explain exactly why it's sound, don't write it.
|
|
25
|
+
- **If you don't know, say so.** The Rust ecosystem is vast and evolving. Admitting uncertainty is better than guessing wrong about soundness.
|
|
26
|
+
|
|
27
|
+
## Key Principles
|
|
28
|
+
|
|
29
|
+
### 1. Ownership Is the Architecture
|
|
30
|
+
|
|
31
|
+
Ownership isn't just memory management — it's your design tool:
|
|
32
|
+
- Who owns this data? Who borrows it? For how long?
|
|
33
|
+
- These questions force you to design clear APIs with explicit lifetimes and responsibilities
|
|
34
|
+
- If you're fighting the borrow checker, the design likely needs rethinking — not an `unsafe` escape hatch
|
|
35
|
+
|
|
36
|
+
### 2. Types Encode Invariants
|
|
37
|
+
|
|
38
|
+
```rust
|
|
39
|
+
// Make illegal states unrepresentable
|
|
40
|
+
// Bad: bool flags that can be inconsistent
|
|
41
|
+
struct Connection {
|
|
42
|
+
is_connected: bool,
|
|
43
|
+
is_authenticated: bool, // Can this be true if is_connected is false?
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Good: State machine as an enum
|
|
47
|
+
enum Connection {
|
|
48
|
+
Disconnected,
|
|
49
|
+
Connected(TcpStream),
|
|
50
|
+
Authenticated { stream: TcpStream, token: AuthToken },
|
|
51
|
+
}
|
|
52
|
+
// It's impossible to be Authenticated without a stream
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. Error Handling Is Explicit
|
|
56
|
+
|
|
57
|
+
```rust
|
|
58
|
+
// Rust has no exceptions. Result and Option are the tools.
|
|
59
|
+
// The ? operator is syntactic sugar, not an excuse to ignore errors.
|
|
60
|
+
|
|
61
|
+
fn load_config(path: &Path) -> Result<Config, ConfigError> {
|
|
62
|
+
let contents = fs::read_to_string(path)
|
|
63
|
+
.map_err(|e| ConfigError::ReadFailed { path: path.to_owned(), source: e })?;
|
|
64
|
+
let config: Config = toml::from_str(&contents)
|
|
65
|
+
.map_err(|e| ConfigError::ParseFailed { source: e })?;
|
|
66
|
+
config.validate()?;
|
|
67
|
+
Ok(config)
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 4. Clippy Is Non-Negotiable
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# In CI, always:
|
|
75
|
+
cargo clippy -- -D warnings
|
|
76
|
+
# Clippy catches real bugs, not just style nits. Treat warnings as errors.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Project Structure
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
my-project/
|
|
83
|
+
├── Cargo.toml # Workspace or package manifest
|
|
84
|
+
├── Cargo.lock # Committed for binaries, not for libraries
|
|
85
|
+
├── src/
|
|
86
|
+
│ ├── main.rs # Binary entry point (or lib.rs for libraries)
|
|
87
|
+
│ ├── lib.rs # Library root — public API surface
|
|
88
|
+
│ ├── config.rs # Configuration loading and validation
|
|
89
|
+
│ ├── error.rs # Error types for this crate
|
|
90
|
+
│ └── domain/ # Core business logic modules
|
|
91
|
+
│ ├── mod.rs
|
|
92
|
+
│ └── ...
|
|
93
|
+
├── tests/ # Integration tests (separate compilation unit)
|
|
94
|
+
│ └── integration_test.rs
|
|
95
|
+
├── benches/ # Benchmarks
|
|
96
|
+
│ └── benchmark.rs
|
|
97
|
+
├── examples/ # Runnable examples
|
|
98
|
+
│ └── basic_usage.rs
|
|
99
|
+
└── build.rs # Build script (if needed)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Workspace Layout (Multi-Crate)
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
workspace/
|
|
106
|
+
├── Cargo.toml # [workspace] members
|
|
107
|
+
├── crates/
|
|
108
|
+
│ ├── core/ # Domain logic — no IO, no async
|
|
109
|
+
│ │ ├── Cargo.toml
|
|
110
|
+
│ │ └── src/lib.rs
|
|
111
|
+
│ ├── api/ # HTTP handlers, routes
|
|
112
|
+
│ │ ├── Cargo.toml
|
|
113
|
+
│ │ └── src/lib.rs
|
|
114
|
+
│ ├── db/ # Database access
|
|
115
|
+
│ │ ├── Cargo.toml
|
|
116
|
+
│ │ └── src/lib.rs
|
|
117
|
+
│ └── cli/ # Binary entry point
|
|
118
|
+
│ ├── Cargo.toml
|
|
119
|
+
│ └── src/main.rs
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Layout Rules
|
|
123
|
+
|
|
124
|
+
- `lib.rs` defines the public API — only `pub` items here are your contract
|
|
125
|
+
- `mod.rs` or file-per-module — be consistent within a project
|
|
126
|
+
- Integration tests in `tests/` are separate compilation units — they test your public API
|
|
127
|
+
- `Cargo.lock` is committed for binaries and applications, not for libraries
|
|
128
|
+
- Feature flags in `Cargo.toml` must be additive — enabling a feature must not break existing code
|
|
129
|
+
|
|
130
|
+
## Definition of Done
|
|
131
|
+
|
|
132
|
+
A Rust feature is complete when:
|
|
133
|
+
- [ ] `cargo build` compiles with zero warnings
|
|
134
|
+
- [ ] `cargo clippy -- -D warnings` passes
|
|
135
|
+
- [ ] `cargo test` passes (including doc tests)
|
|
136
|
+
- [ ] `cargo fmt -- --check` passes
|
|
137
|
+
- [ ] No `unwrap()` or `expect()` in library code without justification
|
|
138
|
+
- [ ] All `unsafe` blocks have `// SAFETY:` comments explaining the invariants
|
|
139
|
+
- [ ] Error types are meaningful and implement `std::error::Error`
|
|
140
|
+
- [ ] Public API has doc comments with examples
|
|
141
|
+
- [ ] No unnecessary allocations in hot paths
|
|
142
|
+
- [ ] `cargo deny check` passes (license and advisory audit)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Rust Ownership and Borrowing
|
|
2
|
+
|
|
3
|
+
Ownership is Rust's defining feature. It's not a constraint — it's a design tool that eliminates entire classes of bugs at compile time. Every memory safety guarantee flows from these rules.
|
|
4
|
+
|
|
5
|
+
## The Three Rules
|
|
6
|
+
|
|
7
|
+
1. Each value has exactly one owner
|
|
8
|
+
2. When the owner goes out of scope, the value is dropped
|
|
9
|
+
3. You can have either one mutable reference OR any number of shared references — never both
|
|
10
|
+
|
|
11
|
+
## Ownership Patterns
|
|
12
|
+
|
|
13
|
+
### Move Semantics
|
|
14
|
+
|
|
15
|
+
```rust
|
|
16
|
+
// Values are moved by default — this is the foundation
|
|
17
|
+
let s = String::from("hello");
|
|
18
|
+
let t = s; // s is MOVED into t
|
|
19
|
+
// println!("{s}"); // Compile error: s has been moved
|
|
20
|
+
|
|
21
|
+
// Functions take ownership unless they borrow
|
|
22
|
+
fn consume(s: String) { /* s is dropped at end of function */ }
|
|
23
|
+
fn borrow(s: &str) { /* s is borrowed — caller keeps ownership */ }
|
|
24
|
+
|
|
25
|
+
// Return values transfer ownership back
|
|
26
|
+
fn create() -> String {
|
|
27
|
+
String::from("created") // Ownership moves to caller
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Borrowing
|
|
32
|
+
|
|
33
|
+
```rust
|
|
34
|
+
// Shared references: &T — read-only, multiple allowed
|
|
35
|
+
fn len(s: &str) -> usize {
|
|
36
|
+
s.len()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Mutable references: &mut T — exclusive access
|
|
40
|
+
fn push_greeting(s: &mut String) {
|
|
41
|
+
s.push_str(", world!");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The borrow checker enforces at compile time:
|
|
45
|
+
let mut s = String::from("hello");
|
|
46
|
+
let r1 = &s; // Fine: shared borrow
|
|
47
|
+
let r2 = &s; // Fine: multiple shared borrows
|
|
48
|
+
println!("{r1} {r2}");
|
|
49
|
+
// r1 and r2 are no longer used after this point (NLL)
|
|
50
|
+
let r3 = &mut s; // Fine: no shared borrows are active
|
|
51
|
+
r3.push_str("!");
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Lifetimes
|
|
55
|
+
|
|
56
|
+
```rust
|
|
57
|
+
// Lifetimes tell the compiler how long references are valid
|
|
58
|
+
// Most of the time, elision handles this automatically
|
|
59
|
+
|
|
60
|
+
// When the compiler needs help:
|
|
61
|
+
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
|
62
|
+
if x.len() > y.len() { x } else { y }
|
|
63
|
+
}
|
|
64
|
+
// Meaning: the returned reference lives at least as long as
|
|
65
|
+
// the shorter of x and y
|
|
66
|
+
|
|
67
|
+
// Structs that hold references need lifetime annotations
|
|
68
|
+
struct Excerpt<'a> {
|
|
69
|
+
text: &'a str,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// If your struct has many lifetime parameters, consider
|
|
73
|
+
// owning the data instead — complexity isn't always worth it
|
|
74
|
+
struct Article {
|
|
75
|
+
title: String, // Owned — simpler, no lifetime tracking
|
|
76
|
+
body: String,
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Interior Mutability
|
|
81
|
+
|
|
82
|
+
```rust
|
|
83
|
+
// When you need mutation behind a shared reference
|
|
84
|
+
|
|
85
|
+
// Cell<T> — for Copy types, single-threaded
|
|
86
|
+
use std::cell::Cell;
|
|
87
|
+
let counter = Cell::new(0);
|
|
88
|
+
counter.set(counter.get() + 1); // Mutation through shared reference
|
|
89
|
+
|
|
90
|
+
// RefCell<T> — runtime borrow checking, single-threaded
|
|
91
|
+
use std::cell::RefCell;
|
|
92
|
+
let data = RefCell::new(vec![1, 2, 3]);
|
|
93
|
+
data.borrow_mut().push(4); // Panics if already borrowed
|
|
94
|
+
|
|
95
|
+
// Mutex<T> / RwLock<T> — thread-safe interior mutability
|
|
96
|
+
use std::sync::Mutex;
|
|
97
|
+
let shared = Mutex::new(Vec::new());
|
|
98
|
+
shared.lock().unwrap().push(42);
|
|
99
|
+
|
|
100
|
+
// Choose the right tool:
|
|
101
|
+
// Cell — Copy types, no overhead, single-threaded
|
|
102
|
+
// RefCell — any type, runtime cost, single-threaded, panics on violation
|
|
103
|
+
// Mutex — any type, blocking, multi-threaded
|
|
104
|
+
// RwLock — any type, read-heavy workloads, multi-threaded
|
|
105
|
+
// Atomic* — primitives, lock-free, multi-threaded
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Smart Pointers
|
|
109
|
+
|
|
110
|
+
```rust
|
|
111
|
+
// Box<T> — heap allocation with single ownership
|
|
112
|
+
let boxed: Box<dyn Error> = Box::new(MyError::new("failed"));
|
|
113
|
+
// Use for: trait objects, recursive types, large values you want on heap
|
|
114
|
+
|
|
115
|
+
// Rc<T> — reference-counted, single-threaded shared ownership
|
|
116
|
+
use std::rc::Rc;
|
|
117
|
+
let shared = Rc::new(ExpensiveData::new());
|
|
118
|
+
let clone = Rc::clone(&shared); // Increments count, doesn't clone data
|
|
119
|
+
// Use for: graph structures, multiple owners in single-threaded code
|
|
120
|
+
|
|
121
|
+
// Arc<T> — atomic reference-counted, thread-safe shared ownership
|
|
122
|
+
use std::sync::Arc;
|
|
123
|
+
let shared = Arc::new(Config::load());
|
|
124
|
+
let handle = {
|
|
125
|
+
let shared = Arc::clone(&shared);
|
|
126
|
+
thread::spawn(move || process(&shared))
|
|
127
|
+
};
|
|
128
|
+
// Use for: shared immutable data across threads
|
|
129
|
+
|
|
130
|
+
// Cow<T> — clone-on-write
|
|
131
|
+
use std::borrow::Cow;
|
|
132
|
+
fn process(input: &str) -> Cow<'_, str> {
|
|
133
|
+
if input.contains("bad") {
|
|
134
|
+
Cow::Owned(input.replace("bad", "good"))
|
|
135
|
+
} else {
|
|
136
|
+
Cow::Borrowed(input) // No allocation when not needed
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Use for: avoiding allocation when mutation is conditional
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Common Borrow Checker Patterns
|
|
143
|
+
|
|
144
|
+
### Splitting Borrows
|
|
145
|
+
|
|
146
|
+
```rust
|
|
147
|
+
// The borrow checker tracks borrows per-field, not per-struct
|
|
148
|
+
struct State {
|
|
149
|
+
buffer: Vec<u8>,
|
|
150
|
+
index: usize,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
impl State {
|
|
154
|
+
fn process(&mut self) {
|
|
155
|
+
// This works because buffer and index are separate fields
|
|
156
|
+
let buf = &self.buffer[self.index..];
|
|
157
|
+
self.index += buf.len();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Temporary Lifetimes
|
|
163
|
+
|
|
164
|
+
```rust
|
|
165
|
+
// Bad: temporary dropped while borrowed
|
|
166
|
+
// let r = &String::from("temp"); // Won't compile — temporary is dropped
|
|
167
|
+
|
|
168
|
+
// Good: bind to a variable to extend the lifetime
|
|
169
|
+
let s = String::from("temp");
|
|
170
|
+
let r = &s; // s lives as long as the scope
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Entry API for Maps
|
|
174
|
+
|
|
175
|
+
```rust
|
|
176
|
+
use std::collections::HashMap;
|
|
177
|
+
|
|
178
|
+
let mut map = HashMap::new();
|
|
179
|
+
|
|
180
|
+
// Entry API avoids double lookup and borrow conflicts
|
|
181
|
+
map.entry("key")
|
|
182
|
+
.and_modify(|v| *v += 1)
|
|
183
|
+
.or_insert(0);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Anti-Patterns
|
|
187
|
+
|
|
188
|
+
```rust
|
|
189
|
+
// Never: Clone to satisfy the borrow checker without understanding why
|
|
190
|
+
let data = expensive_data.clone(); // Are you sure this is necessary?
|
|
191
|
+
// If you're cloning to fix a borrow error, first ask: can I restructure the code?
|
|
192
|
+
|
|
193
|
+
// Never: Leaking memory to avoid lifetimes
|
|
194
|
+
let leaked: &'static str = Box::leak(Box::new(String::from("forever")));
|
|
195
|
+
// This is almost never the right solution
|
|
196
|
+
|
|
197
|
+
// Never: Rc<RefCell<T>> as a default — it's a code smell
|
|
198
|
+
// If everything is Rc<RefCell<T>>, you've recreated garbage-collected mutable state
|
|
199
|
+
// Restructure to use ownership and borrowing properly first
|
|
200
|
+
|
|
201
|
+
// Never: Ignoring the borrow checker by reaching for unsafe
|
|
202
|
+
// If the borrow checker rejects your code, the design usually needs to change
|
|
203
|
+
// unsafe doesn't fix design problems — it hides them
|
|
204
|
+
```
|