agentic-team-templates 0.9.2 → 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 +36 -0
- package/src/index.test.js +9 -0
- package/templates/javascript-expert/.cursorrules/language-deep-dive.md +245 -0
- package/templates/javascript-expert/.cursorrules/node-patterns.md +184 -0
- package/templates/javascript-expert/.cursorrules/overview.md +130 -0
- package/templates/javascript-expert/.cursorrules/performance.md +203 -0
- package/templates/javascript-expert/.cursorrules/react-patterns.md +249 -0
- package/templates/javascript-expert/.cursorrules/testing.md +403 -0
- package/templates/javascript-expert/.cursorrules/tooling.md +176 -0
- package/templates/javascript-expert/CLAUDE.md +448 -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,300 @@
|
|
|
1
|
+
# Rust Testing
|
|
2
|
+
|
|
3
|
+
Rust has testing built into the language and toolchain. `cargo test` runs unit tests, integration tests, and doc tests in one command.
|
|
4
|
+
|
|
5
|
+
## Unit Tests
|
|
6
|
+
|
|
7
|
+
### Module-Level Tests
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
// Tests live in the same file as the code they test
|
|
11
|
+
pub fn celsius_to_fahrenheit(c: f64) -> f64 {
|
|
12
|
+
c * 9.0 / 5.0 + 32.0
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[cfg(test)]
|
|
16
|
+
mod tests {
|
|
17
|
+
use super::*;
|
|
18
|
+
|
|
19
|
+
#[test]
|
|
20
|
+
fn freezing_point() {
|
|
21
|
+
assert!((celsius_to_fahrenheit(0.0) - 32.0).abs() < f64::EPSILON);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[test]
|
|
25
|
+
fn boiling_point() {
|
|
26
|
+
assert!((celsius_to_fahrenheit(100.0) - 212.0).abs() < f64::EPSILON);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[test]
|
|
30
|
+
fn negative_temperature() {
|
|
31
|
+
assert!((celsius_to_fahrenheit(-40.0) - -40.0).abs() < f64::EPSILON);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Testing Error Cases
|
|
37
|
+
|
|
38
|
+
```rust
|
|
39
|
+
#[test]
|
|
40
|
+
fn parse_invalid_input_returns_error() {
|
|
41
|
+
let result = parse_config("not valid toml");
|
|
42
|
+
assert!(result.is_err());
|
|
43
|
+
|
|
44
|
+
let err = result.unwrap_err();
|
|
45
|
+
assert!(err.to_string().contains("expected"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[test]
|
|
49
|
+
fn empty_name_fails_validation() {
|
|
50
|
+
let input = CreateUserInput { name: "".into(), email: "a@b.com".into() };
|
|
51
|
+
let result = validate_user(&input);
|
|
52
|
+
|
|
53
|
+
assert!(matches!(result, Err(ValidationError::EmptyField { field }) if field == "name"));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Testing that something panics
|
|
57
|
+
#[test]
|
|
58
|
+
#[should_panic(expected = "index out of bounds")]
|
|
59
|
+
fn panics_on_out_of_bounds() {
|
|
60
|
+
let v = vec![1, 2, 3];
|
|
61
|
+
let _ = v[10];
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Test Organization
|
|
66
|
+
|
|
67
|
+
```rust
|
|
68
|
+
// #[cfg(test)] ensures test code is never compiled into release builds
|
|
69
|
+
#[cfg(test)]
|
|
70
|
+
mod tests {
|
|
71
|
+
use super::*;
|
|
72
|
+
|
|
73
|
+
// Shared test fixtures
|
|
74
|
+
fn sample_user() -> User {
|
|
75
|
+
User {
|
|
76
|
+
id: UserId::new("test-123"),
|
|
77
|
+
name: "Alice".into(),
|
|
78
|
+
email: "alice@example.com".into(),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Group related tests with nested modules
|
|
83
|
+
mod validation {
|
|
84
|
+
use super::*;
|
|
85
|
+
|
|
86
|
+
#[test]
|
|
87
|
+
fn rejects_empty_name() { ... }
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn rejects_invalid_email() { ... }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
mod serialization {
|
|
94
|
+
use super::*;
|
|
95
|
+
|
|
96
|
+
#[test]
|
|
97
|
+
fn round_trips_through_json() { ... }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Integration Tests
|
|
103
|
+
|
|
104
|
+
```rust
|
|
105
|
+
// tests/api_test.rs — separate compilation unit, tests public API only
|
|
106
|
+
use my_crate::{Config, Server};
|
|
107
|
+
|
|
108
|
+
#[test]
|
|
109
|
+
fn server_responds_to_health_check() {
|
|
110
|
+
let config = Config::default();
|
|
111
|
+
let server = Server::new(config);
|
|
112
|
+
|
|
113
|
+
let response = server.handle_request("/healthz");
|
|
114
|
+
|
|
115
|
+
assert_eq!(response.status(), 200);
|
|
116
|
+
assert_eq!(response.body(), r#"{"status":"ok"}"#);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Async Tests
|
|
121
|
+
|
|
122
|
+
```rust
|
|
123
|
+
#[tokio::test]
|
|
124
|
+
async fn fetches_user_by_id() {
|
|
125
|
+
let db = setup_test_db().await;
|
|
126
|
+
let repo = UserRepo::new(db);
|
|
127
|
+
|
|
128
|
+
let user = repo.find_by_id("123").await.unwrap();
|
|
129
|
+
|
|
130
|
+
assert_eq!(user.name, "Alice");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// With custom runtime config
|
|
134
|
+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
135
|
+
async fn concurrent_operations() {
|
|
136
|
+
// ...
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Property-Based Testing
|
|
141
|
+
|
|
142
|
+
```rust
|
|
143
|
+
use proptest::prelude::*;
|
|
144
|
+
|
|
145
|
+
proptest! {
|
|
146
|
+
#[test]
|
|
147
|
+
fn parse_never_panics(s in "\\PC*") {
|
|
148
|
+
// Whatever string we throw at parse, it must not panic
|
|
149
|
+
let _ = parse_input(&s);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[test]
|
|
153
|
+
fn serialization_round_trips(
|
|
154
|
+
name in "[a-zA-Z]{1,50}",
|
|
155
|
+
age in 0u32..150,
|
|
156
|
+
) {
|
|
157
|
+
let user = User { name, age };
|
|
158
|
+
let json = serde_json::to_string(&user).unwrap();
|
|
159
|
+
let decoded: User = serde_json::from_str(&json).unwrap();
|
|
160
|
+
assert_eq!(user, decoded);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Doc Tests
|
|
166
|
+
|
|
167
|
+
```rust
|
|
168
|
+
/// Adds two numbers together.
|
|
169
|
+
///
|
|
170
|
+
/// # Examples
|
|
171
|
+
///
|
|
172
|
+
/// ```
|
|
173
|
+
/// use my_crate::add;
|
|
174
|
+
///
|
|
175
|
+
/// assert_eq!(add(2, 3), 5);
|
|
176
|
+
/// assert_eq!(add(-1, 1), 0);
|
|
177
|
+
/// ```
|
|
178
|
+
///
|
|
179
|
+
/// # Panics
|
|
180
|
+
///
|
|
181
|
+
/// Panics if the result overflows.
|
|
182
|
+
///
|
|
183
|
+
/// ```should_panic
|
|
184
|
+
/// use my_crate::add;
|
|
185
|
+
///
|
|
186
|
+
/// add(i64::MAX, 1); // Overflow!
|
|
187
|
+
/// ```
|
|
188
|
+
pub fn add(a: i64, b: i64) -> i64 {
|
|
189
|
+
a.checked_add(b).expect("overflow")
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Doc tests are compiled and run — they're both documentation AND tests.
|
|
193
|
+
// They verify your examples actually work.
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Mocking and Test Doubles
|
|
197
|
+
|
|
198
|
+
```rust
|
|
199
|
+
// Prefer trait-based dependency injection over mocking frameworks
|
|
200
|
+
|
|
201
|
+
// Define trait at the consumer
|
|
202
|
+
trait Clock {
|
|
203
|
+
fn now(&self) -> DateTime<Utc>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Production implementation
|
|
207
|
+
struct SystemClock;
|
|
208
|
+
impl Clock for SystemClock {
|
|
209
|
+
fn now(&self) -> DateTime<Utc> { Utc::now() }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Test implementation — deterministic
|
|
213
|
+
struct FakeClock(DateTime<Utc>);
|
|
214
|
+
impl Clock for FakeClock {
|
|
215
|
+
fn now(&self) -> DateTime<Utc> { self.0 }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#[test]
|
|
219
|
+
fn token_expires_after_one_hour() {
|
|
220
|
+
let fixed_time = Utc.with_ymd_and_hms(2025, 1, 1, 12, 0, 0).unwrap();
|
|
221
|
+
let clock = FakeClock(fixed_time);
|
|
222
|
+
let token = generate_token(&clock);
|
|
223
|
+
|
|
224
|
+
let check_time = fixed_time + Duration::hours(2);
|
|
225
|
+
assert!(token.is_expired_at(check_time));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// When you do need a mocking library: mockall
|
|
229
|
+
#[automock]
|
|
230
|
+
trait Database {
|
|
231
|
+
fn get(&self, key: &str) -> Option<String>;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#[test]
|
|
235
|
+
fn returns_cached_value() {
|
|
236
|
+
let mut mock = MockDatabase::new();
|
|
237
|
+
mock.expect_get()
|
|
238
|
+
.with(eq("user:123"))
|
|
239
|
+
.returning(|_| Some("Alice".into()));
|
|
240
|
+
|
|
241
|
+
let service = Service::new(mock);
|
|
242
|
+
assert_eq!(service.get_user("123"), Some("Alice".into()));
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Benchmarks
|
|
247
|
+
|
|
248
|
+
```rust
|
|
249
|
+
// Using criterion (de facto standard)
|
|
250
|
+
// benches/my_benchmark.rs
|
|
251
|
+
use criterion::{criterion_group, criterion_main, Criterion, black_box};
|
|
252
|
+
|
|
253
|
+
fn bench_parse(c: &mut Criterion) {
|
|
254
|
+
let input = include_str!("../testdata/large_input.txt");
|
|
255
|
+
|
|
256
|
+
c.bench_function("parse_large_input", |b| {
|
|
257
|
+
b.iter(|| parse(black_box(input)))
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fn bench_comparison(c: &mut Criterion) {
|
|
262
|
+
let mut group = c.benchmark_group("serialization");
|
|
263
|
+
let data = generate_test_data();
|
|
264
|
+
|
|
265
|
+
group.bench_function("json", |b| {
|
|
266
|
+
b.iter(|| serde_json::to_vec(black_box(&data)))
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
group.bench_function("bincode", |b| {
|
|
270
|
+
b.iter(|| bincode::serialize(black_box(&data)))
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
group.finish();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
criterion_group!(benches, bench_parse, bench_comparison);
|
|
277
|
+
criterion_main!(benches);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Test Anti-Patterns
|
|
281
|
+
|
|
282
|
+
```rust
|
|
283
|
+
// Never: Tests that depend on execution order
|
|
284
|
+
// Each test runs in its own thread — no shared mutable state
|
|
285
|
+
|
|
286
|
+
// Never: Ignoring tests instead of fixing them
|
|
287
|
+
#[ignore] // Why? For how long? File an issue.
|
|
288
|
+
|
|
289
|
+
// Never: Testing private internals through hacks
|
|
290
|
+
// If you can't test it through the public API, the design needs work
|
|
291
|
+
|
|
292
|
+
// Never: Assertions without messages in complex tests
|
|
293
|
+
assert!(result.is_ok()); // What was the input? What was the error?
|
|
294
|
+
// Better:
|
|
295
|
+
assert!(result.is_ok(), "expected Ok for input {input:?}, got {result:?}");
|
|
296
|
+
|
|
297
|
+
// Never: Flaky tests
|
|
298
|
+
// Flaky = broken. Fix the race condition or the timing dependency.
|
|
299
|
+
// Use deterministic clocks, seeded RNGs, and proper synchronization.
|
|
300
|
+
```
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Rust Traits and Generics
|
|
2
|
+
|
|
3
|
+
Traits are Rust's mechanism for abstraction, polymorphism, and code reuse. Combined with generics, they enable zero-cost abstractions that rival hand-written specialized code.
|
|
4
|
+
|
|
5
|
+
## Trait Design
|
|
6
|
+
|
|
7
|
+
### Small, Focused Traits
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
// Good: Single-purpose traits
|
|
11
|
+
pub trait Validate {
|
|
12
|
+
fn validate(&self) -> Result<(), ValidationError>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub trait Serialize {
|
|
16
|
+
fn serialize(&self, writer: &mut dyn Write) -> Result<(), SerializeError>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Bad: God trait
|
|
20
|
+
pub trait Entity {
|
|
21
|
+
fn validate(&self) -> Result<(), Error>;
|
|
22
|
+
fn serialize(&self) -> Vec<u8>;
|
|
23
|
+
fn save(&self, db: &Database) -> Result<(), Error>;
|
|
24
|
+
fn render(&self) -> Html;
|
|
25
|
+
// Too many responsibilities — split by concern
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Extension Traits
|
|
30
|
+
|
|
31
|
+
```rust
|
|
32
|
+
// Add methods to existing types via extension traits
|
|
33
|
+
pub trait StrExt {
|
|
34
|
+
fn is_blank(&self) -> bool;
|
|
35
|
+
fn truncate_to(&self, max_len: usize) -> &str;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl StrExt for str {
|
|
39
|
+
fn is_blank(&self) -> bool {
|
|
40
|
+
self.trim().is_empty()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn truncate_to(&self, max_len: usize) -> &str {
|
|
44
|
+
if self.len() <= max_len {
|
|
45
|
+
self
|
|
46
|
+
} else {
|
|
47
|
+
// Find a char boundary to avoid panic
|
|
48
|
+
let mut end = max_len;
|
|
49
|
+
while !self.is_char_boundary(end) {
|
|
50
|
+
end -= 1;
|
|
51
|
+
}
|
|
52
|
+
&self[..end]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Sealed Traits
|
|
59
|
+
|
|
60
|
+
```rust
|
|
61
|
+
// Prevent external implementations when your trait is part of an internal contract
|
|
62
|
+
mod private {
|
|
63
|
+
pub trait Sealed {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub trait Backend: private::Sealed {
|
|
67
|
+
fn execute(&self, query: &str) -> Result<Rows>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Only types in your crate can implement Sealed, so only they can implement Backend
|
|
71
|
+
impl private::Sealed for PostgresBackend {}
|
|
72
|
+
impl Backend for PostgresBackend { ... }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### The Newtype Pattern
|
|
76
|
+
|
|
77
|
+
```rust
|
|
78
|
+
// Implement foreign traits on foreign types via newtype
|
|
79
|
+
struct Meters(f64);
|
|
80
|
+
struct Seconds(f64);
|
|
81
|
+
|
|
82
|
+
impl fmt::Display for Meters {
|
|
83
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
84
|
+
write!(f, "{:.2}m", self.0)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Also prevents mixing units at compile time
|
|
89
|
+
fn speed(distance: Meters, time: Seconds) -> f64 {
|
|
90
|
+
distance.0 / time.0
|
|
91
|
+
}
|
|
92
|
+
// speed(Seconds(1.0), Meters(5.0)) — compile error! Arguments are swapped.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Generics
|
|
96
|
+
|
|
97
|
+
### Trait Bounds
|
|
98
|
+
|
|
99
|
+
```rust
|
|
100
|
+
// Prefer impl Trait for simple cases
|
|
101
|
+
fn print_all(items: &[impl Display]) {
|
|
102
|
+
for item in items {
|
|
103
|
+
println!("{item}");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Use where clauses for complex bounds
|
|
108
|
+
fn merge<T, U>(left: T, right: U) -> Merged
|
|
109
|
+
where
|
|
110
|
+
T: IntoIterator<Item = Record>,
|
|
111
|
+
U: IntoIterator<Item = Record>,
|
|
112
|
+
T::IntoIter: ExactSizeIterator,
|
|
113
|
+
{
|
|
114
|
+
// ...
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Use explicit generic parameters when the caller needs to specify the type
|
|
118
|
+
fn parse<T: FromStr>(input: &str) -> Result<T, T::Err> {
|
|
119
|
+
input.parse()
|
|
120
|
+
}
|
|
121
|
+
let n: i32 = parse("42")?;
|
|
122
|
+
let f: f64 = parse("3.14")?;
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Associated Types vs Generic Parameters
|
|
126
|
+
|
|
127
|
+
```rust
|
|
128
|
+
// Associated types: one implementation per type
|
|
129
|
+
trait Iterator {
|
|
130
|
+
type Item; // Each iterator has exactly one Item type
|
|
131
|
+
fn next(&mut self) -> Option<Self::Item>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Generic parameters: multiple implementations per type
|
|
135
|
+
trait Convert<T> {
|
|
136
|
+
fn convert(&self) -> T;
|
|
137
|
+
}
|
|
138
|
+
// A single type can implement Convert<String>, Convert<i32>, etc.
|
|
139
|
+
|
|
140
|
+
// Rule of thumb: If there should be only one implementation
|
|
141
|
+
// for a given Self type, use an associated type.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Phantom Types
|
|
145
|
+
|
|
146
|
+
```rust
|
|
147
|
+
use std::marker::PhantomData;
|
|
148
|
+
|
|
149
|
+
// Type-state pattern: encode state in the type system
|
|
150
|
+
struct Validated;
|
|
151
|
+
struct Unvalidated;
|
|
152
|
+
|
|
153
|
+
struct Form<State> {
|
|
154
|
+
data: FormData,
|
|
155
|
+
_state: PhantomData<State>,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
impl Form<Unvalidated> {
|
|
159
|
+
fn new(data: FormData) -> Self {
|
|
160
|
+
Form { data, _state: PhantomData }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn validate(self) -> Result<Form<Validated>, ValidationError> {
|
|
164
|
+
// validation logic...
|
|
165
|
+
Ok(Form { data: self.data, _state: PhantomData })
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
impl Form<Validated> {
|
|
170
|
+
fn submit(self) -> Result<(), SubmitError> {
|
|
171
|
+
// Only validated forms can be submitted
|
|
172
|
+
send(self.data)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Form::new(data).submit() — compile error! Must validate first.
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Dynamic Dispatch
|
|
180
|
+
|
|
181
|
+
```rust
|
|
182
|
+
// Static dispatch (monomorphization) — zero cost, larger binary
|
|
183
|
+
fn process(handler: impl Handler) { handler.handle(); }
|
|
184
|
+
|
|
185
|
+
// Dynamic dispatch (trait objects) — vtable indirection, smaller binary
|
|
186
|
+
fn process(handler: &dyn Handler) { handler.handle(); }
|
|
187
|
+
|
|
188
|
+
// Use trait objects when:
|
|
189
|
+
// - You need a heterogeneous collection
|
|
190
|
+
// - You want to reduce binary size (e.g., embedded)
|
|
191
|
+
// - The type is determined at runtime
|
|
192
|
+
|
|
193
|
+
// Trait object safety: a trait is object-safe if:
|
|
194
|
+
// - No methods return Self
|
|
195
|
+
// - No methods have generic type parameters
|
|
196
|
+
// - No associated functions (no &self receiver)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Derive and Common Traits
|
|
200
|
+
|
|
201
|
+
```rust
|
|
202
|
+
// Derive what you can — it's correct and free
|
|
203
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
204
|
+
pub struct UserId(String);
|
|
205
|
+
|
|
206
|
+
// Standard trait hierarchy to consider:
|
|
207
|
+
// Debug — always derive, essential for diagnostics
|
|
208
|
+
// Clone — when values need to be duplicated
|
|
209
|
+
// PartialEq/Eq — when values need comparison
|
|
210
|
+
// Hash — when used as map keys (requires Eq)
|
|
211
|
+
// Default — when a meaningful zero value exists
|
|
212
|
+
// Display — for user-facing output (implement manually)
|
|
213
|
+
// Serialize/Deserialize — for serde (derive with feature flag)
|
|
214
|
+
|
|
215
|
+
// Don't derive Copy unless the type is truly trivially copyable
|
|
216
|
+
// and you want implicit copies. Copy types can't have Drop.
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Anti-Patterns
|
|
220
|
+
|
|
221
|
+
```rust
|
|
222
|
+
// Never: Trait objects everywhere when generics would work
|
|
223
|
+
// Box<dyn Fn()> is fine for callbacks stored in structs
|
|
224
|
+
// impl Fn() is better for function parameters
|
|
225
|
+
|
|
226
|
+
// Never: Overusing generics for types that will only ever have one concrete type
|
|
227
|
+
fn process<T: Into<String>>(name: T) { } // Just take String or &str
|
|
228
|
+
|
|
229
|
+
// Never: Unused type parameters
|
|
230
|
+
struct Wrapper<T> { // T is not used — won't compile without PhantomData
|
|
231
|
+
data: Vec<u8>,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Never: Implementing traits for types you don't own without a newtype
|
|
235
|
+
// Orphan rule prevents this anyway — respect it
|
|
236
|
+
```
|