devflow-kit 1.1.0 → 1.2.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/CHANGELOG.md +39 -0
- package/README.md +23 -6
- package/dist/plugins.js +67 -3
- package/package.json +2 -1
- package/plugins/devflow-accessibility/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-ambient/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +1 -1
- package/plugins/devflow-ambient/skills/ambient-router/references/skill-catalog.md +4 -0
- package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -4
- package/plugins/devflow-code-review/agents/reviewer.md +8 -0
- package/plugins/devflow-code-review/commands/code-review-teams.md +11 -1
- package/plugins/devflow-code-review/commands/code-review.md +12 -2
- package/plugins/devflow-core-skills/.claude-plugin/plugin.json +2 -6
- package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/skills/go/SKILL.md +187 -0
- package/plugins/devflow-go/skills/go/references/concurrency.md +312 -0
- package/plugins/devflow-go/skills/go/references/detection.md +129 -0
- package/plugins/devflow-go/skills/go/references/patterns.md +232 -0
- package/plugins/devflow-go/skills/go/references/violations.md +205 -0
- package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -3
- package/plugins/devflow-implement/agents/coder.md +11 -6
- package/plugins/devflow-java/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-java/skills/java/SKILL.md +183 -0
- package/plugins/devflow-java/skills/java/references/detection.md +120 -0
- package/plugins/devflow-java/skills/java/references/modern-java.md +270 -0
- package/plugins/devflow-java/skills/java/references/patterns.md +235 -0
- package/plugins/devflow-java/skills/java/references/violations.md +213 -0
- package/plugins/devflow-python/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-python/skills/python/SKILL.md +188 -0
- package/plugins/devflow-python/skills/python/references/async.md +220 -0
- package/plugins/devflow-python/skills/python/references/detection.md +128 -0
- package/plugins/devflow-python/skills/python/references/patterns.md +226 -0
- package/plugins/devflow-python/skills/python/references/violations.md +204 -0
- package/plugins/devflow-react/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/SKILL.md +1 -1
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/references/patterns.md +3 -3
- package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-rust/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-rust/skills/rust/SKILL.md +193 -0
- package/plugins/devflow-rust/skills/rust/references/detection.md +131 -0
- package/plugins/devflow-rust/skills/rust/references/ownership.md +242 -0
- package/plugins/devflow-rust/skills/rust/references/patterns.md +210 -0
- package/plugins/devflow-rust/skills/rust/references/violations.md +191 -0
- package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-typescript/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/patterns.md +3 -3
- package/shared/agents/coder.md +11 -6
- package/shared/agents/reviewer.md +8 -0
- package/shared/skills/ambient-router/SKILL.md +1 -1
- package/shared/skills/ambient-router/references/skill-catalog.md +4 -0
- package/shared/skills/go/SKILL.md +187 -0
- package/shared/skills/go/references/concurrency.md +312 -0
- package/shared/skills/go/references/detection.md +129 -0
- package/shared/skills/go/references/patterns.md +232 -0
- package/shared/skills/go/references/violations.md +205 -0
- package/shared/skills/java/SKILL.md +183 -0
- package/shared/skills/java/references/detection.md +120 -0
- package/shared/skills/java/references/modern-java.md +270 -0
- package/shared/skills/java/references/patterns.md +235 -0
- package/shared/skills/java/references/violations.md +213 -0
- package/shared/skills/python/SKILL.md +188 -0
- package/shared/skills/python/references/async.md +220 -0
- package/shared/skills/python/references/detection.md +128 -0
- package/shared/skills/python/references/patterns.md +226 -0
- package/shared/skills/python/references/violations.md +204 -0
- package/shared/skills/react/SKILL.md +1 -1
- package/shared/skills/react/references/patterns.md +3 -3
- package/shared/skills/rust/SKILL.md +193 -0
- package/shared/skills/rust/references/detection.md +131 -0
- package/shared/skills/rust/references/ownership.md +242 -0
- package/shared/skills/rust/references/patterns.md +210 -0
- package/shared/skills/rust/references/violations.md +191 -0
- package/shared/skills/typescript/references/patterns.md +3 -3
- package/plugins/devflow-code-review/skills/react/SKILL.md +0 -276
- package/plugins/devflow-code-review/skills/react/references/patterns.md +0 -1331
- package/plugins/devflow-core-skills/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-core-skills/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-core-skills/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-core-skills/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-core-skills/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-core-skills/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-core-skills/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-core-skills/skills/frontend-design/references/violations.md +0 -453
- package/plugins/devflow-core-skills/skills/react/references/violations.md +0 -565
- package/plugins/devflow-implement/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-implement/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-implement/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-implement/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-implement/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-implement/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-implement/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-implement/skills/frontend-design/references/violations.md +0 -453
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-react}/skills/react/references/violations.md +0 -0
- /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/SKILL.md +0 -0
- /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/violations.md +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Rust Detection Patterns
|
|
2
|
+
|
|
3
|
+
Grep and regex patterns for finding common Rust issues. Use with `Grep` tool.
|
|
4
|
+
|
|
5
|
+
## Unwrap and Expect
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Find .unwrap() calls (exclude tests)
|
|
9
|
+
grep -rn '\.unwrap()' --include='*.rs' --exclude-dir=tests --exclude='*_test.rs'
|
|
10
|
+
|
|
11
|
+
# Find .expect() without descriptive message
|
|
12
|
+
grep -rn '\.expect("")' --include='*.rs'
|
|
13
|
+
|
|
14
|
+
# Find unwrap_or_default hiding errors
|
|
15
|
+
grep -rn '\.unwrap_or_default()' --include='*.rs'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Pattern**: `\.unwrap\(\)` — matches any `.unwrap()` call
|
|
19
|
+
**Pattern**: `\.expect\("` — matches `.expect("` to review message quality
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Clone Abuse
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Find .clone() calls — review each for necessity
|
|
27
|
+
grep -rn '\.clone()' --include='*.rs'
|
|
28
|
+
|
|
29
|
+
# Find clone in loop bodies (likely hot-path waste)
|
|
30
|
+
grep -rn -A2 'for.*in' --include='*.rs' | grep '\.clone()'
|
|
31
|
+
|
|
32
|
+
# Find to_string() where &str would work
|
|
33
|
+
grep -rn '\.to_string()' --include='*.rs'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Pattern**: `\.clone\(\)` — all clone calls for manual review
|
|
37
|
+
**Pattern**: `\.to_owned\(\)` — ownership transfer that may be unnecessary
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Unsafe Blocks
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Find all unsafe blocks
|
|
45
|
+
grep -rn 'unsafe\s*{' --include='*.rs'
|
|
46
|
+
|
|
47
|
+
# Find unsafe without SAFETY comment
|
|
48
|
+
grep -rn -B2 'unsafe\s*{' --include='*.rs' | grep -v 'SAFETY'
|
|
49
|
+
|
|
50
|
+
# Find unsafe functions
|
|
51
|
+
grep -rn 'unsafe fn' --include='*.rs'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Pattern**: `unsafe\s*\{` — unsafe blocks
|
|
55
|
+
**Pattern**: `unsafe fn` — unsafe function declarations
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Incomplete Code
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Find todo! and unimplemented! macros
|
|
63
|
+
grep -rn 'todo!\|unimplemented!' --include='*.rs'
|
|
64
|
+
|
|
65
|
+
# Find unreachable! that may hide bugs
|
|
66
|
+
grep -rn 'unreachable!' --include='*.rs'
|
|
67
|
+
|
|
68
|
+
# Find panic! in non-test code
|
|
69
|
+
grep -rn 'panic!' --include='*.rs' --exclude-dir=tests --exclude='*_test.rs'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Pattern**: `todo!\(\)` — placeholder code
|
|
73
|
+
**Pattern**: `unimplemented!\(\)` — unfinished implementations
|
|
74
|
+
**Pattern**: `panic!\(` — explicit panics outside tests
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Error Handling Issues
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Find ignored Results (let _ = expr that returns Result)
|
|
82
|
+
grep -rn 'let _ =' --include='*.rs'
|
|
83
|
+
|
|
84
|
+
# Find empty match arms that may swallow errors
|
|
85
|
+
grep -rn '=> {}' --include='*.rs'
|
|
86
|
+
|
|
87
|
+
# Find catch-all match arms hiding missing cases
|
|
88
|
+
grep -rn '_ =>' --include='*.rs'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Pattern**: `let _ =` — potentially ignored Result or important value
|
|
92
|
+
**Pattern**: `=> \{\}` — empty match arm (may swallow error)
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Concurrency Red Flags
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Find static mut (almost always wrong)
|
|
100
|
+
grep -rn 'static mut' --include='*.rs'
|
|
101
|
+
|
|
102
|
+
# Find blocking calls in async functions
|
|
103
|
+
grep -rn 'std::fs::' --include='*.rs' | grep -v 'test'
|
|
104
|
+
grep -rn 'std::thread::sleep' --include='*.rs'
|
|
105
|
+
|
|
106
|
+
# Find Mutex without Arc in multi-threaded context
|
|
107
|
+
grep -rn 'Mutex::new' --include='*.rs'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Pattern**: `static mut` — mutable global state (data race risk)
|
|
111
|
+
**Pattern**: `std::fs::` — blocking I/O that may appear in async context
|
|
112
|
+
**Pattern**: `std::thread::sleep` — blocking sleep (use `tokio::time::sleep` in async)
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Clippy Lints
|
|
117
|
+
|
|
118
|
+
Run Clippy for automated detection of many patterns above:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cargo clippy -- -D warnings
|
|
122
|
+
cargo clippy -- -W clippy::pedantic
|
|
123
|
+
cargo clippy -- -W clippy::nursery
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Key Clippy lints that catch issues:
|
|
127
|
+
- `clippy::unwrap_used` — flags unwrap calls
|
|
128
|
+
- `clippy::clone_on_ref_ptr` — unnecessary Arc/Rc clone
|
|
129
|
+
- `clippy::needless_pass_by_value` — should borrow instead
|
|
130
|
+
- `clippy::missing_errors_doc` — public Result fn without doc
|
|
131
|
+
- `clippy::wildcard_enum_match_arm` — catch-all hiding cases
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Rust Ownership Deep Dive
|
|
2
|
+
|
|
3
|
+
Advanced ownership patterns, lifetime elision, interior mutability, and pinning.
|
|
4
|
+
|
|
5
|
+
## Lifetime Elision Rules
|
|
6
|
+
|
|
7
|
+
The compiler applies three rules to infer lifetimes. When they don't resolve, annotate manually.
|
|
8
|
+
|
|
9
|
+
### Rule 1: Each Reference Parameter Gets Its Own Lifetime
|
|
10
|
+
|
|
11
|
+
```rust
|
|
12
|
+
// Compiler sees: fn first(s: &str) -> &str
|
|
13
|
+
// Compiler infers: fn first<'a>(s: &'a str) -> &'a str
|
|
14
|
+
fn first(s: &str) -> &str {
|
|
15
|
+
&s[..1]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Rule 2: Single Input Lifetime Applies to All Outputs
|
|
20
|
+
|
|
21
|
+
```rust
|
|
22
|
+
// One input reference — output borrows from it
|
|
23
|
+
fn trim(s: &str) -> &str {
|
|
24
|
+
s.trim()
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Rule 3: &self Lifetime Applies to All Outputs in Methods
|
|
29
|
+
|
|
30
|
+
```rust
|
|
31
|
+
impl Config {
|
|
32
|
+
// &self lifetime flows to return
|
|
33
|
+
fn database_url(&self) -> &str {
|
|
34
|
+
&self.db_url
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### When Elision Fails
|
|
40
|
+
|
|
41
|
+
```rust
|
|
42
|
+
// Two input lifetimes — compiler can't decide which output borrows from
|
|
43
|
+
// Must annotate: output borrows from `a`, not `b`
|
|
44
|
+
fn longest<'a>(a: &'a str, b: &str) -> &'a str {
|
|
45
|
+
if a.len() >= b.len() { a } else { a }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Interior Mutability
|
|
52
|
+
|
|
53
|
+
Mutate data behind a shared reference when ownership rules are too strict.
|
|
54
|
+
|
|
55
|
+
### Cell — Copy Types Only
|
|
56
|
+
|
|
57
|
+
```rust
|
|
58
|
+
use std::cell::Cell;
|
|
59
|
+
|
|
60
|
+
struct Counter {
|
|
61
|
+
count: Cell<u32>, // Mutate through &self
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
impl Counter {
|
|
65
|
+
fn increment(&self) {
|
|
66
|
+
self.count.set(self.count.get() + 1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### RefCell — Runtime Borrow Checking
|
|
72
|
+
|
|
73
|
+
```rust
|
|
74
|
+
use std::cell::RefCell;
|
|
75
|
+
|
|
76
|
+
struct Cache {
|
|
77
|
+
data: RefCell<HashMap<String, String>>,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl Cache {
|
|
81
|
+
fn get_or_insert(&self, key: &str, value: &str) -> String {
|
|
82
|
+
let mut data = self.data.borrow_mut(); // Panics if already borrowed
|
|
83
|
+
data.entry(key.to_string())
|
|
84
|
+
.or_insert_with(|| value.to_string())
|
|
85
|
+
.clone()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Mutex — Thread-Safe Interior Mutability
|
|
91
|
+
|
|
92
|
+
```rust
|
|
93
|
+
use std::sync::Mutex;
|
|
94
|
+
|
|
95
|
+
struct SharedState {
|
|
96
|
+
data: Mutex<Vec<String>>,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
impl SharedState {
|
|
100
|
+
fn push(&self, item: String) -> Result<(), AppError> {
|
|
101
|
+
let mut data = self.data.lock()
|
|
102
|
+
.map_err(|_| AppError::LockPoisoned)?;
|
|
103
|
+
data.push(item);
|
|
104
|
+
Ok(())
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Decision Guide
|
|
110
|
+
|
|
111
|
+
| Type | Thread-Safe | Cost | Use Case |
|
|
112
|
+
|------|-------------|------|----------|
|
|
113
|
+
| `Cell<T>` | No | Zero | Copy types, single-threaded |
|
|
114
|
+
| `RefCell<T>` | No | Runtime borrow check | Non-Copy, single-threaded |
|
|
115
|
+
| `Mutex<T>` | Yes | Lock overhead | Multi-threaded mutation |
|
|
116
|
+
| `RwLock<T>` | Yes | Lock overhead | Multi-threaded, read-heavy |
|
|
117
|
+
| `Atomic*` | Yes | Hardware atomic | Counters, flags |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Cow — Clone on Write
|
|
122
|
+
|
|
123
|
+
Defer cloning until mutation is actually needed.
|
|
124
|
+
|
|
125
|
+
```rust
|
|
126
|
+
use std::borrow::Cow;
|
|
127
|
+
|
|
128
|
+
// Returns borrowed if no processing needed, owned if modified
|
|
129
|
+
fn normalize_path(path: &str) -> Cow<'_, str> {
|
|
130
|
+
if path.contains("//") {
|
|
131
|
+
Cow::Owned(path.replace("//", "/"))
|
|
132
|
+
} else {
|
|
133
|
+
Cow::Borrowed(path)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Function accepts both owned and borrowed transparently
|
|
138
|
+
fn process(input: Cow<'_, str>) {
|
|
139
|
+
println!("{}", input); // No allocation if already borrowed
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Cow in APIs
|
|
144
|
+
|
|
145
|
+
```rust
|
|
146
|
+
// Accept Cow for flexible ownership — caller decides allocation
|
|
147
|
+
pub fn log_message(msg: Cow<'_, str>) {
|
|
148
|
+
eprintln!("[LOG] {}", msg);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Caller with borrowed data — zero-copy
|
|
152
|
+
log_message(Cow::Borrowed("static message"));
|
|
153
|
+
|
|
154
|
+
// Caller with owned data — no extra clone
|
|
155
|
+
log_message(Cow::Owned(format!("dynamic: {}", value)));
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Pin for Async and Self-Referential Types
|
|
161
|
+
|
|
162
|
+
### Why Pin Exists
|
|
163
|
+
|
|
164
|
+
Self-referential structs break if moved in memory. `Pin` guarantees the value won't move.
|
|
165
|
+
|
|
166
|
+
```rust
|
|
167
|
+
use std::pin::Pin;
|
|
168
|
+
use std::future::Future;
|
|
169
|
+
|
|
170
|
+
// Async functions return self-referential futures
|
|
171
|
+
// Pin ensures the future stays in place while polled
|
|
172
|
+
fn fetch_data(url: &str) -> Pin<Box<dyn Future<Output = Result<Data, Error>> + '_>> {
|
|
173
|
+
Box::pin(async move {
|
|
174
|
+
let response = reqwest::get(url).await?;
|
|
175
|
+
let data = response.json::<Data>().await?;
|
|
176
|
+
Ok(data)
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Pin in Practice
|
|
182
|
+
|
|
183
|
+
```rust
|
|
184
|
+
use tokio::pin;
|
|
185
|
+
|
|
186
|
+
async fn process_stream(stream: impl Stream<Item = Data>) {
|
|
187
|
+
// pin! macro pins the stream to the stack
|
|
188
|
+
pin!(stream);
|
|
189
|
+
|
|
190
|
+
while let Some(item) = stream.next().await {
|
|
191
|
+
handle(item).await;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### When You Need Pin
|
|
197
|
+
|
|
198
|
+
| Scenario | Need Pin? |
|
|
199
|
+
|----------|-----------|
|
|
200
|
+
| Returning `async` blocks as trait objects | Yes |
|
|
201
|
+
| Implementing `Future` manually | Yes |
|
|
202
|
+
| Using `tokio::select!` on futures | Yes (automatically handled) |
|
|
203
|
+
| Normal async/await | No (compiler handles it) |
|
|
204
|
+
| Storing futures in collections | Yes (`Pin<Box<dyn Future>>`) |
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Ownership Transfer Patterns
|
|
209
|
+
|
|
210
|
+
### Take Pattern — Move Out of Option
|
|
211
|
+
|
|
212
|
+
```rust
|
|
213
|
+
struct Connection {
|
|
214
|
+
session: Option<Session>,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
impl Connection {
|
|
218
|
+
fn close(&mut self) -> Option<Session> {
|
|
219
|
+
self.session.take() // Moves out, leaves None
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Swap Pattern — Replace In Place
|
|
225
|
+
|
|
226
|
+
```rust
|
|
227
|
+
use std::mem;
|
|
228
|
+
|
|
229
|
+
fn rotate_buffer(current: &mut Vec<u8>, new_data: Vec<u8>) -> Vec<u8> {
|
|
230
|
+
mem::replace(current, new_data) // Returns old, installs new
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Entry Pattern — Conditional Insertion
|
|
235
|
+
|
|
236
|
+
```rust
|
|
237
|
+
use std::collections::HashMap;
|
|
238
|
+
|
|
239
|
+
fn get_or_create(map: &mut HashMap<String, Vec<Item>>, key: &str) -> &mut Vec<Item> {
|
|
240
|
+
map.entry(key.to_string()).or_insert_with(Vec::new)
|
|
241
|
+
}
|
|
242
|
+
```
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Rust Extended Patterns
|
|
2
|
+
|
|
3
|
+
Extended correct patterns for Rust. Reference from main SKILL.md.
|
|
4
|
+
|
|
5
|
+
## Typestate Pattern
|
|
6
|
+
|
|
7
|
+
Encode valid state transitions in the type system so invalid sequences don't compile.
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
// States are zero-sized types — no runtime cost
|
|
11
|
+
struct Draft;
|
|
12
|
+
struct Published;
|
|
13
|
+
struct Archived;
|
|
14
|
+
|
|
15
|
+
struct Article<State> {
|
|
16
|
+
title: String,
|
|
17
|
+
body: String,
|
|
18
|
+
_state: std::marker::PhantomData<State>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl Article<Draft> {
|
|
22
|
+
pub fn new(title: String, body: String) -> Self {
|
|
23
|
+
Article { title, body, _state: std::marker::PhantomData }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn publish(self) -> Article<Published> {
|
|
27
|
+
Article { title: self.title, body: self.body, _state: std::marker::PhantomData }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl Article<Published> {
|
|
32
|
+
pub fn archive(self) -> Article<Archived> {
|
|
33
|
+
Article { title: self.title, body: self.body, _state: std::marker::PhantomData }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// article.archive() on Draft won't compile — transition enforced at compile time
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Error Handling Hierarchy
|
|
43
|
+
|
|
44
|
+
Layer errors from specific to general using `thiserror` for libraries and `anyhow` for applications.
|
|
45
|
+
|
|
46
|
+
```rust
|
|
47
|
+
// Library: precise, typed errors
|
|
48
|
+
#[derive(thiserror::Error, Debug)]
|
|
49
|
+
pub enum RepoError {
|
|
50
|
+
#[error("entity {entity} with id {id} not found")]
|
|
51
|
+
NotFound { entity: &'static str, id: String },
|
|
52
|
+
#[error("duplicate key: {0}")]
|
|
53
|
+
Duplicate(String),
|
|
54
|
+
#[error("connection failed")]
|
|
55
|
+
Connection(#[from] sqlx::Error),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Application: ergonomic error propagation
|
|
59
|
+
use anyhow::{Context, Result};
|
|
60
|
+
|
|
61
|
+
fn run() -> Result<()> {
|
|
62
|
+
let config = load_config()
|
|
63
|
+
.context("failed to load configuration")?;
|
|
64
|
+
let db = connect_db(&config.database_url)
|
|
65
|
+
.context("failed to connect to database")?;
|
|
66
|
+
serve(db, config.port)
|
|
67
|
+
.context("server exited with error")
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Trait Objects vs Generics
|
|
74
|
+
|
|
75
|
+
### Use Generics for Performance (Monomorphization)
|
|
76
|
+
|
|
77
|
+
```rust
|
|
78
|
+
fn largest<T: PartialOrd>(list: &[T]) -> Option<&T> {
|
|
79
|
+
list.iter().reduce(|a, b| if a >= b { a } else { b })
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Use Trait Objects for Heterogeneous Collections
|
|
84
|
+
|
|
85
|
+
```rust
|
|
86
|
+
trait Handler: Send + Sync {
|
|
87
|
+
fn handle(&self, request: &Request) -> Response;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
struct Router {
|
|
91
|
+
routes: Vec<Box<dyn Handler>>, // Different concrete types in one Vec
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Decision Guide
|
|
96
|
+
|
|
97
|
+
| Criteria | Generics | Trait Objects |
|
|
98
|
+
|----------|----------|--------------|
|
|
99
|
+
| Known types at compile time | Yes | No |
|
|
100
|
+
| Heterogeneous collection | No | Yes |
|
|
101
|
+
| Performance-critical | Yes | Acceptable overhead |
|
|
102
|
+
| Binary size concern | Increases | Minimal |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Smart Pointers
|
|
107
|
+
|
|
108
|
+
### Box — Heap Allocation
|
|
109
|
+
|
|
110
|
+
```rust
|
|
111
|
+
// Recursive types require indirection
|
|
112
|
+
enum List<T> {
|
|
113
|
+
Cons(T, Box<List<T>>),
|
|
114
|
+
Nil,
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Rc/Arc — Shared Ownership
|
|
119
|
+
|
|
120
|
+
```rust
|
|
121
|
+
use std::sync::Arc;
|
|
122
|
+
|
|
123
|
+
// Shared read-only config across threads
|
|
124
|
+
let config = Arc::new(load_config()?);
|
|
125
|
+
let config_clone = Arc::clone(&config);
|
|
126
|
+
tokio::spawn(async move {
|
|
127
|
+
use_config(&config_clone).await;
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### When to Use Each
|
|
132
|
+
|
|
133
|
+
| Pointer | Use Case |
|
|
134
|
+
|---------|----------|
|
|
135
|
+
| `Box<T>` | Single owner, heap allocation, recursive types |
|
|
136
|
+
| `Rc<T>` | Multiple owners, single-threaded |
|
|
137
|
+
| `Arc<T>` | Multiple owners, multi-threaded |
|
|
138
|
+
| `Cow<'a, T>` | Clone-on-write, flexible borrowing |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## From/Into Conversions
|
|
143
|
+
|
|
144
|
+
```rust
|
|
145
|
+
// Implement From for automatic Into
|
|
146
|
+
impl From<CreateUserRequest> for User {
|
|
147
|
+
fn from(req: CreateUserRequest) -> Self {
|
|
148
|
+
User {
|
|
149
|
+
id: Uuid::new_v4(),
|
|
150
|
+
name: req.name,
|
|
151
|
+
email: req.email,
|
|
152
|
+
created_at: Utc::now(),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Callers get Into for free
|
|
158
|
+
fn save_user(user: impl Into<User>) -> Result<(), DbError> {
|
|
159
|
+
let user: User = user.into();
|
|
160
|
+
// ...
|
|
161
|
+
Ok(())
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Derive and Trait Best Practices
|
|
168
|
+
|
|
169
|
+
```rust
|
|
170
|
+
// Derive the standard set for data types
|
|
171
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
172
|
+
pub struct UserId(String);
|
|
173
|
+
|
|
174
|
+
// Derive serde for serialization boundaries
|
|
175
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
176
|
+
pub struct ApiResponse<T> {
|
|
177
|
+
pub data: T,
|
|
178
|
+
pub metadata: Metadata,
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Trait Implementation Order Convention
|
|
183
|
+
|
|
184
|
+
1. Standard library traits (`Debug`, `Display`, `Clone`, `PartialEq`)
|
|
185
|
+
2. Conversion traits (`From`, `Into`, `TryFrom`)
|
|
186
|
+
3. Iterator traits (`Iterator`, `IntoIterator`)
|
|
187
|
+
4. Serde traits (`Serialize`, `Deserialize`)
|
|
188
|
+
5. Custom traits (domain-specific)
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Module Organization
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
src/
|
|
196
|
+
├── lib.rs # Public API re-exports
|
|
197
|
+
├── error.rs # Crate-level error types
|
|
198
|
+
├── domain/
|
|
199
|
+
│ ├── mod.rs # Domain re-exports
|
|
200
|
+
│ ├── user.rs # User entity and logic
|
|
201
|
+
│ └── order.rs # Order entity and logic
|
|
202
|
+
├── repo/
|
|
203
|
+
│ ├── mod.rs # Repository trait definitions
|
|
204
|
+
│ └── postgres.rs # Concrete implementation
|
|
205
|
+
└── api/
|
|
206
|
+
├── mod.rs # Route registration
|
|
207
|
+
└── handlers.rs # HTTP handlers
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Keep `lib.rs` thin — re-export only the public API. Internal modules use `pub(crate)`.
|