@booklib/skills 1.3.2 → 1.5.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.
Files changed (37) hide show
  1. package/AGENTS.md +108 -0
  2. package/CLAUDE.md +58 -0
  3. package/CODE_OF_CONDUCT.md +31 -0
  4. package/CONTRIBUTING.md +13 -0
  5. package/README.md +69 -45
  6. package/SECURITY.md +9 -0
  7. package/assets/logo.svg +36 -0
  8. package/demo.gif +0 -0
  9. package/demo.tape +40 -0
  10. package/docs/index.html +187 -0
  11. package/package.json +2 -2
  12. package/skills/effective-typescript/SKILL.md +166 -0
  13. package/skills/effective-typescript/evals/evals.json +36 -0
  14. package/skills/effective-typescript/examples/after.md +70 -0
  15. package/skills/effective-typescript/examples/before.md +47 -0
  16. package/skills/effective-typescript/references/api_reference.md +118 -0
  17. package/skills/effective-typescript/references/practices-catalog.md +371 -0
  18. package/skills/programming-with-rust/SKILL.md +194 -0
  19. package/skills/programming-with-rust/evals/evals.json +37 -0
  20. package/skills/programming-with-rust/examples/after.md +107 -0
  21. package/skills/programming-with-rust/examples/before.md +59 -0
  22. package/skills/programming-with-rust/references/api_reference.md +152 -0
  23. package/skills/programming-with-rust/references/practices-catalog.md +335 -0
  24. package/skills/rust-in-action/SKILL.md +290 -0
  25. package/skills/rust-in-action/evals/evals.json +38 -0
  26. package/skills/rust-in-action/examples/after.md +156 -0
  27. package/skills/rust-in-action/examples/before.md +56 -0
  28. package/skills/rust-in-action/references/practices-catalog.md +346 -0
  29. package/skills/rust-in-action/scripts/review.py +147 -0
  30. package/skills/skill-router/SKILL.md +16 -13
  31. package/skills/skill-router/references/skill-catalog.md +19 -1
  32. package/skills/spring-boot-in-action/SKILL.md +312 -0
  33. package/skills/spring-boot-in-action/evals/evals.json +39 -0
  34. package/skills/spring-boot-in-action/examples/after.md +185 -0
  35. package/skills/spring-boot-in-action/examples/before.md +84 -0
  36. package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
  37. package/skills/spring-boot-in-action/scripts/review.py +184 -0
@@ -0,0 +1,371 @@
1
+ # Effective TypeScript — Practices Catalog
2
+
3
+ Deep before/after examples for the 20 most impactful items.
4
+
5
+ ---
6
+
7
+ ## Item 2: Know Which TypeScript Options You're Using
8
+
9
+ Always use `strict: true`. It enables `noImplicitAny` and `strictNullChecks` which catch the most common TypeScript bugs.
10
+
11
+ **Before:**
12
+ ```json
13
+ { "compilerOptions": {} }
14
+ ```
15
+ **After:**
16
+ ```json
17
+ { "compilerOptions": { "strict": true, "target": "ES2020", "module": "commonjs" } }
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Item 9: Prefer Type Declarations to Type Assertions
23
+
24
+ **Before:**
25
+ ```typescript
26
+ const user = {} as User; // bypasses type checking — user is actually empty
27
+ const input = document.getElementById('name') as HTMLInputElement;
28
+ ```
29
+ **After:**
30
+ ```typescript
31
+ const user: User = { id: '1', name: 'Alice', email: 'a@example.com' }; // checked
32
+ const input = document.getElementById('name');
33
+ if (input instanceof HTMLInputElement) {
34
+ console.log(input.value); // narrowed, not asserted
35
+ }
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Item 10: Avoid Object Wrapper Types
41
+
42
+ **Before:**
43
+ ```typescript
44
+ function greet(name: String) { // String, not string
45
+ return 'Hello ' + name;
46
+ }
47
+ const s = new String('world');
48
+ greet(s); // works but s !== 'world' as a primitive
49
+ ```
50
+ **After:**
51
+ ```typescript
52
+ function greet(name: string) { // primitive type
53
+ return 'Hello ' + name;
54
+ }
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Item 13: Know the Differences Between `type` and `interface`
60
+
61
+ - Use `interface` for object shapes that consumers may need to extend (open for augmentation)
62
+ - Use `type` for unions, intersections, tuples, and mapped types (cannot be augmented)
63
+
64
+ **Before:**
65
+ ```typescript
66
+ type User = { id: string; name: string }; // fine, but can't be augmented by consumers
67
+ type StringOrNumber = string | number; // correct use of type
68
+ ```
69
+ **After:**
70
+ ```typescript
71
+ interface User { id: string; name: string; } // open for extension
72
+ type StringOrNumber = string | number; // unions must be type aliases
73
+ type ReadonlyUser = Readonly<User>; // mapped type must be type alias
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Item 14: Use Type Operations and Generics to Avoid Repeating Yourself
79
+
80
+ **Before:**
81
+ ```typescript
82
+ interface SavedState { userId: string; name: string; lastSaved: Date; }
83
+ interface UnsavedState { userId: string; name: string; } // repeated fields
84
+ ```
85
+ **After:**
86
+ ```typescript
87
+ interface State { userId: string; name: string; }
88
+ interface SavedState extends State { lastSaved: Date; }
89
+ // Or with Pick/Omit:
90
+ type UnsavedState = Omit<SavedState, 'lastSaved'>;
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Item 17: Use `readonly` to Avoid Errors Associated with Mutation
96
+
97
+ **Before:**
98
+ ```typescript
99
+ function sort(arr: number[]): number[] {
100
+ return arr.sort(); // mutates the original array!
101
+ }
102
+ ```
103
+ **After:**
104
+ ```typescript
105
+ function sort(arr: readonly number[]): number[] {
106
+ return [...arr].sort(); // forced to copy — cannot mutate readonly input
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Item 22: Understand Type Narrowing
113
+
114
+ **Before:**
115
+ ```typescript
116
+ function processInput(val: string | null) {
117
+ console.log(val.toUpperCase()); // error: val is possibly null
118
+ }
119
+ ```
120
+ **After:**
121
+ ```typescript
122
+ function processInput(val: string | null) {
123
+ if (val === null) return;
124
+ console.log(val.toUpperCase()); // narrowed to string
125
+ }
126
+
127
+ // instanceof narrowing
128
+ function format(val: Date | string) {
129
+ if (val instanceof Date) return val.toISOString();
130
+ return val.toUpperCase();
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Item 25: Use async Functions Instead of Callbacks for Asynchronous Code
137
+
138
+ Callbacks produce `any`-typed errors and complex nested types. `async`/`await` lets TypeScript infer `Promise<T>` cleanly.
139
+
140
+ **Before:**
141
+ ```typescript
142
+ function fetchData(url: string, cb: (err: any, data: any) => void) {
143
+ fetch(url)
144
+ .then(r => r.json())
145
+ .then(d => cb(null, d))
146
+ .catch(e => cb(e, null));
147
+ }
148
+ ```
149
+ **After:**
150
+ ```typescript
151
+ async function fetchData<T>(url: string): Promise<T> {
152
+ const response = await fetch(url);
153
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
154
+ return response.json() as T;
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Item 28: Prefer Types That Always Represent Valid States
161
+
162
+ **Before:**
163
+ ```typescript
164
+ interface Page {
165
+ pageText: string;
166
+ isLoading: boolean;
167
+ error: string; // What does isLoading:true + error:'...' mean?
168
+ }
169
+ ```
170
+ **After:**
171
+ ```typescript
172
+ type Page =
173
+ | { state: 'loading' }
174
+ | { state: 'success'; pageText: string }
175
+ | { state: 'error'; error: string };
176
+ // Every combination is meaningful — no impossible states
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Item 31: Push Null Values to the Perimeter
182
+
183
+ **Before:**
184
+ ```typescript
185
+ // null scattered throughout
186
+ function getUser(id: string | null): User | null {
187
+ if (!id) return null;
188
+ const user: User | null = db.find(id) ?? null;
189
+ return user;
190
+ }
191
+ ```
192
+ **After:**
193
+ ```typescript
194
+ // null handled once at the boundary
195
+ function getUser(id: string): User {
196
+ const user = db.find(id);
197
+ if (!user) throw new Error(`User ${id} not found`);
198
+ return user;
199
+ }
200
+ // Callers who might not have an id handle it at their boundary
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Item 32: Prefer Unions of Interfaces to Interfaces of Unions
206
+
207
+ **Before:**
208
+ ```typescript
209
+ interface Layer {
210
+ type: 'fill' | 'line' | 'point';
211
+ fillColor?: string; // only for fill
212
+ lineWidth?: number; // only for line
213
+ pointRadius?: number; // only for point
214
+ // { type: 'fill', lineWidth: 5 } is representable but invalid
215
+ }
216
+ ```
217
+ **After:**
218
+ ```typescript
219
+ interface FillLayer { type: 'fill'; fillColor: string; }
220
+ interface LineLayer { type: 'line'; lineWidth: number; }
221
+ interface PointLayer { type: 'point'; pointRadius: number; }
222
+ type Layer = FillLayer | LineLayer | PointLayer;
223
+ // Each valid state has exactly the right fields
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Item 33: Prefer More Precise Alternatives to String Types
229
+
230
+ **Before:**
231
+ ```typescript
232
+ function setAlignment(align: string) {} // accepts anything
233
+ interface Album { genre: string; } // 'rock', 'jazz', or 'anything'?
234
+ ```
235
+ **After:**
236
+ ```typescript
237
+ type Alignment = 'left' | 'center' | 'right';
238
+ function setAlignment(align: Alignment) {}
239
+
240
+ type Genre = 'rock' | 'jazz' | 'pop' | 'classical';
241
+ interface Album { genre: Genre; }
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Item 38: Use the Narrowest Possible Scope for `any` Types
247
+
248
+ **Before:**
249
+ ```typescript
250
+ const config: any = JSON.parse(rawConfig); // entire variable is any
251
+ console.log(config.timeout); // no type checking from here on
252
+ ```
253
+ **After:**
254
+ ```typescript
255
+ const config = JSON.parse(rawConfig) as { timeout: number; retries: number };
256
+ // or better — parse with unknown and narrow:
257
+ const raw: unknown = JSON.parse(rawConfig);
258
+ const timeout = (raw as { timeout: number }).timeout; // any scoped to one property access
259
+ ```
260
+
261
+ ---
262
+
263
+ ## Item 40: Hide Unsafe Type Assertions in Well-Typed Functions
264
+
265
+ **Before:**
266
+ ```typescript
267
+ // Assertion visible at every call site
268
+ const user = fetchUser() as User;
269
+ ```
270
+ **After:**
271
+ ```typescript
272
+ // Assertion encapsulated once — all callers get proper types
273
+ async function fetchUser(id: string): Promise<User> {
274
+ const raw: unknown = await fetch(`/api/users/${id}`).then(r => r.json());
275
+ return raw as User; // single controlled assertion inside typed boundary
276
+ }
277
+ ```
278
+
279
+ ---
280
+
281
+ ## Item 42: Use `unknown` Instead of `any` for Values with an Unknown Type
282
+
283
+ **Before:**
284
+ ```typescript
285
+ function parseYaml(yaml: string): any { // callers never have to narrow
286
+ return parse(yaml);
287
+ }
288
+ const config = parseYaml(raw);
289
+ config.port.toFixed(); // no error — but crashes if port is missing
290
+ ```
291
+ **After:**
292
+ ```typescript
293
+ function parseYaml(yaml: string): unknown { // callers must narrow before use
294
+ return parse(yaml);
295
+ }
296
+ const config = parseYaml(raw);
297
+ if (typeof config === 'object' && config !== null && 'port' in config) {
298
+ const port = (config as { port: number }).port;
299
+ }
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Item 47: Export All Types That Appear in Public APIs
305
+
306
+ **Before:**
307
+ ```typescript
308
+ // Unexported — users have to use ReturnType<typeof getUser> hacks
309
+ interface User { id: string; name: string; }
310
+ export function getUser(id: string): User { ... }
311
+ ```
312
+ **After:**
313
+ ```typescript
314
+ export interface User { id: string; name: string; } // exported explicitly
315
+ export function getUser(id: string): User { ... }
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Item 48: Use TSDoc for API Comments
321
+
322
+ **Before:**
323
+ ```typescript
324
+ // Fetches user by id. Returns null if not found.
325
+ function getUser(id: string): User | null { ... }
326
+ ```
327
+ **After:**
328
+ ```typescript
329
+ /**
330
+ * Fetches a user by their unique identifier.
331
+ *
332
+ * @param id - The user's unique identifier
333
+ * @returns The user, or null if no user exists with that id
334
+ * @throws {NetworkError} If the network request fails
335
+ */
336
+ function getUser(id: string): User | null { ... }
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Item 53: Prefer ECMAScript Features to TypeScript Features
342
+
343
+ Avoid TypeScript-specific features that don't map cleanly to JavaScript. Prefer standard ES features.
344
+
345
+ **Before:**
346
+ ```typescript
347
+ // TypeScript enums — compiled to objects with side effects, can't be tree-shaken
348
+ enum Direction { North, South, East, West }
349
+ ```
350
+ **After:**
351
+ ```typescript
352
+ // Const object + type — tree-shakeable, maps cleanly to JS
353
+ const Direction = { North: 'North', South: 'South', East: 'East', West: 'West' } as const;
354
+ type Direction = typeof Direction[keyof typeof Direction];
355
+ ```
356
+
357
+ ---
358
+
359
+ ## Item 62: Don't Consider Migration Complete Until You Enable `noImplicitAny`
360
+
361
+ The final step of any JS→TS migration. Without it, TypeScript silently accepts untyped code.
362
+
363
+ ```json
364
+ // tsconfig.json — final migration milestone
365
+ {
366
+ "compilerOptions": {
367
+ "strict": true, // includes noImplicitAny and strictNullChecks
368
+ "noImplicitAny": true // explicit — every value must have a type
369
+ }
370
+ }
371
+ ```
@@ -0,0 +1,194 @@
1
+ ---
2
+ name: programming-with-rust
3
+ description: >
4
+ Write and review Rust code using practices from "Programming with Rust" by Donis Marshall.
5
+ Covers ownership, borrowing, lifetimes, error handling with Result/Option, traits, generics,
6
+ pattern matching, closures, fearless concurrency, and macros. Use when writing Rust, reviewing
7
+ Rust code, or learning Rust idioms. Trigger on: "Rust", "ownership", "borrow checker",
8
+ "lifetimes", "Result", "Option", "traits", "fearless concurrency", ".rs files", "cargo".
9
+ ---
10
+
11
+ # Programming with Rust Skill
12
+
13
+ Apply the practices from Donis Marshall's "Programming with Rust" to review existing code and write new Rust. This skill operates in two modes: **Review Mode** (analyze code for violations of Rust idioms) and **Write Mode** (produce safe, idiomatic Rust from scratch).
14
+
15
+ ## Reference Files
16
+
17
+ - `ref-01-fundamentals.md` — Ch 1-3: Rust model, tooling, variables, primitives, references
18
+ - `ref-02-types-strings.md` — Ch 4-5: String vs &str, formatting, Display/Debug traits
19
+ - `ref-03-control-collections.md` — Ch 6-7: Control flow, iterators, arrays, Vec, HashMap
20
+ - `ref-04-ownership-lifetimes.md` — Ch 8-10: Ownership, move semantics, borrowing, lifetimes
21
+ - `ref-05-functions-errors.md` — Ch 11-12: Functions, Result, Option, panics, custom errors
22
+ - `ref-06-structs-generics.md` — Ch 13-14: Structs, impl blocks, generics, bounds
23
+ - `ref-07-patterns-closures.md` — Ch 15-16: Pattern matching, closures, Fn/FnMut/FnOnce
24
+ - `ref-08-traits.md` — Ch 17: Trait definition, dispatch, supertraits, associated types
25
+ - `ref-09-concurrency.md` — Ch 18-19: Threads, channels, Mutex, RwLock, atomics
26
+ - `ref-10-advanced.md` — Ch 20-23: Memory, interior mutability, macros, FFI, modules
27
+
28
+ ## How to Use This Skill
29
+
30
+ **Before responding**, read the reference files relevant to the code's topic. For ownership/borrowing issues read `ref-04`. For error handling read `ref-05`. For a full review, read all files.
31
+
32
+ ---
33
+
34
+ ## Mode 1: Code Review
35
+
36
+ When the user asks you to **review** Rust code, follow this process:
37
+
38
+ ### Step 1: Read Relevant References
39
+ Identify which chapters apply. If unsure, read all reference files.
40
+
41
+ ### Step 2: Analyze the Code
42
+
43
+ Check these areas in order of severity:
44
+
45
+ 1. **Ownership & Borrowing** (Ch 8, 10): Unnecessary `.clone()` calls? Mutable borrow conflicts? Move semantics misunderstood?
46
+ 2. **Lifetimes** (Ch 9): Missing or incorrect annotations? Can elision rules eliminate them? `'static` used where a shorter lifetime would do?
47
+ 3. **Error Handling** (Ch 12): Is `.unwrap()` or `.expect()` used where `?` operator or proper matching belongs? Are custom error types missing where they'd help callers?
48
+ 4. **Traits & Generics** (Ch 14, 17): Are trait bounds as narrow as possible? Is dynamic dispatch (`dyn Trait`) used where static dispatch (`impl Trait`) is better for performance?
49
+ 5. **Pattern Matching** (Ch 15): Are `match` arms exhaustive? Can `if let` / `while let` simplify single-arm matches? Are wildcards masking unhandled cases?
50
+ 6. **Concurrency** (Ch 18, 19): Is shared state protected by `Mutex` or `RwLock`? Are channels used correctly? Is `Arc` used when `Rc` would suffice (single-threaded)?
51
+ 7. **Memory** (Ch 20): Is `RefCell` used outside of single-threaded interior mutability? Is `Box` used unnecessarily when stack allocation would work?
52
+ 8. **Idioms**: Is `for item in collection` preferred over manual indexing? Are iterator adapters (`map`, `filter`, `collect`) used over manual loops?
53
+
54
+ ### Step 3: Report Findings
55
+ For each issue, report:
56
+ - **Chapter reference** (e.g., "Ch 12: Error Handling")
57
+ - **Location** in the code
58
+ - **What's wrong** (the anti-pattern)
59
+ - **How to fix it** (the idiomatic Rust approach)
60
+ - **Priority**: Critical (safety/correctness), Important (idiom/maintainability), Suggestion (polish)
61
+
62
+ ### Step 4: Provide Fixed Code
63
+ Offer a corrected version with comments explaining each change.
64
+
65
+ ---
66
+
67
+ ## Mode 2: Writing New Code
68
+
69
+ When the user asks you to **write** new Rust code, apply these core principles:
70
+
71
+ ### Ownership & Memory Safety
72
+
73
+ 1. **Prefer borrowing over cloning** (Ch 8). Pass `&T` or `&mut T` rather than transferring ownership when the caller still needs the value. Clone only when ownership genuinely needs to be duplicated.
74
+
75
+ 2. **Respect the single-owner rule** (Ch 8). Each value has exactly one owner. When you move a value, the old binding is invalid — design data flow around this.
76
+
77
+ 3. **Use lifetime elision** (Ch 9). Annotate lifetimes only when the compiler cannot infer them. Explicit annotations are for structs holding references and functions with multiple reference parameters where elision is ambiguous.
78
+
79
+ 4. **Prefer `&str` over `String` in function parameters** (Ch 4). Accept `&str` to work with both `String` (via deref coercion) and string literals without allocation.
80
+
81
+ ### Error Handling
82
+
83
+ 5. **Return `Result<T, E>`, never panic in library code** (Ch 12). Panics are for unrecoverable programmer errors. Use `Result` for anything that can fail at runtime.
84
+
85
+ 6. **Use the `?` operator for error propagation** (Ch 12). Replace `.unwrap()` chains with `?` to propagate errors cleanly to callers.
86
+
87
+ 7. **Define custom error types for public APIs** (Ch 12). Implement `std::error::Error` and use `thiserror` or manual `impl` to give callers structured errors they can match on.
88
+
89
+ 8. **Never use `.unwrap()` in production paths** (Ch 12). Use `.expect("reason")` only in tests or where panic is truly the right response. In all other cases, handle with `match`, `if let`, or `?`.
90
+
91
+ ### Traits & Generics
92
+
93
+ 9. **Prefer `impl Trait` over `dyn Trait` for return types** (Ch 17). Static dispatch is zero-cost. Use `dyn Trait` only when you need runtime polymorphism with mixed types in a collection.
94
+
95
+ 10. **Use trait bounds instead of concrete types** (Ch 14). `fn process<T: Display + Debug>(item: T)` is more reusable than accepting a concrete type.
96
+
97
+ 11. **Implement standard traits** (Ch 17). Derive `Debug`, `Clone`, `PartialEq` where appropriate. Implement `Display` for user-facing output, `From`/`Into` for conversions.
98
+
99
+ ### Pattern Matching
100
+
101
+ 12. **Use `match` for exhaustive handling** (Ch 15). The compiler enforces exhaustiveness — treat it as a feature, not a burden.
102
+
103
+ 13. **Use `if let` for single-variant matching** (Ch 15). `if let Some(x) = opt { }` is cleaner than a two-arm `match` when you only care about one case.
104
+
105
+ 14. **Destructure in function parameters** (Ch 15). `fn process(&Point { x, y }: &Point)` avoids manual field access inside the body.
106
+
107
+ ### Concurrency
108
+
109
+ 15. **Use channels for message passing** (Ch 18). Prefer `std::sync::mpsc` channels over shared mutable state when threads can communicate by value.
110
+
111
+ 16. **Wrap shared state in `Arc<Mutex<T>>`** (Ch 19). `Arc` for shared ownership across threads, `Mutex` for mutual exclusion. Use `RwLock` when reads vastly outnumber writes.
112
+
113
+ 17. **Prefer `Mutex::lock().unwrap()` with `.expect()`** (Ch 19). Poisoned mutexes indicate a panic in another thread — `.expect("mutex poisoned")` makes this explicit.
114
+
115
+ ### Code Structure Template
116
+
117
+ ```rust
118
+ use std::fmt;
119
+
120
+ /// Domain error type for public APIs (Ch 12)
121
+ #[derive(Debug)]
122
+ pub enum AppError {
123
+ NotFound(String),
124
+ InvalidInput(String),
125
+ Io(std::io::Error),
126
+ }
127
+
128
+ impl fmt::Display for AppError {
129
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130
+ match self {
131
+ AppError::NotFound(msg) => write!(f, "not found: {msg}"),
132
+ AppError::InvalidInput(msg) => write!(f, "invalid input: {msg}"),
133
+ AppError::Io(e) => write!(f, "I/O error: {e}"),
134
+ }
135
+ }
136
+ }
137
+
138
+ impl std::error::Error for AppError {}
139
+
140
+ impl From<std::io::Error> for AppError {
141
+ fn from(e: std::io::Error) -> Self {
142
+ AppError::Io(e)
143
+ }
144
+ }
145
+
146
+ /// Accept &str, not String (Ch 4)
147
+ pub fn find_user(name: &str) -> Result<User, AppError> {
148
+ // Use ? for propagation (Ch 12)
149
+ let data = load_data()?;
150
+ data.iter()
151
+ .find(|u| u.name == name)
152
+ .cloned() // clone only the found item (Ch 8)
153
+ .ok_or_else(|| AppError::NotFound(name.to_string()))
154
+ }
155
+
156
+ /// Derive standard traits (Ch 17)
157
+ #[derive(Debug, Clone, PartialEq)]
158
+ pub struct User {
159
+ pub name: String,
160
+ pub role: Role,
161
+ }
162
+
163
+ /// Enum for valid states (Ch 15)
164
+ #[derive(Debug, Clone, PartialEq)]
165
+ pub enum Role {
166
+ Admin,
167
+ Viewer,
168
+ Editor,
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Priority of Practices by Impact
175
+
176
+ ### Critical (Safety & Correctness)
177
+ - Ch 8: Understand ownership — moving vs borrowing, no use-after-move
178
+ - Ch 10: One mutable borrow OR many immutable borrows — never both
179
+ - Ch 12: Never `.unwrap()` in production; use `Result` and `?`
180
+ - Ch 19: Always protect shared mutable state with `Mutex` or `RwLock`
181
+
182
+ ### Important (Idiom & Maintainability)
183
+ - Ch 4: Prefer `&str` params over `String`
184
+ - Ch 9: Rely on lifetime elision; annotate only when required
185
+ - Ch 12: Custom error types for public APIs
186
+ - Ch 14/17: Trait bounds over concrete types; `impl Trait` over `dyn Trait`
187
+ - Ch 15: Exhaustive `match`; use `if let` for single-arm cases
188
+ - Ch 17: Derive/implement standard traits (`Debug`, `Display`, `From`)
189
+
190
+ ### Suggestions (Polish)
191
+ - Ch 6: Use iterator adapters (`map`, `filter`, `flat_map`) over manual loops
192
+ - Ch 16: Use closures with `move` when capturing environment across thread boundaries
193
+ - Ch 20: Prefer stack allocation; use `Box` only when size is unknown at compile time
194
+ - Ch 21: Use `derive` macros before writing manual `impl` blocks
@@ -0,0 +1,37 @@
1
+ {
2
+ "evals": [
3
+ {
4
+ "id": "eval-01-unwrap-clone-ownership",
5
+ "prompt": "Review this Rust code:\n\n```rust\nfn process_users(users: Vec<String>) -> Vec<String> {\n let copy = users.clone();\n let mut result = Vec::new();\n for i in 0..copy.len() {\n result.push(copy[i].clone());\n }\n result\n}\n\nfn read_file(path: String) -> String {\n std::fs::read_to_string(path).unwrap()\n}\n\nfn find(map: HashMap<String, u32>, key: String) -> u32 {\n map.get(&key).unwrap().clone()\n}\n```",
6
+ "expectations": [
7
+ "Flag Ch 8: users.clone() is unnecessary — borrow &[String] instead of taking ownership of Vec<String>",
8
+ "Flag Ch 6: manual index loop should be replaced with iterator adapters (.iter().cloned().collect())",
9
+ "Flag Ch 12: .unwrap() in read_file will panic on I/O error — should return Result<String, std::io::Error> and use ?",
10
+ "Flag Ch 8: find takes owned HashMap — borrows &HashMap<String, u32> instead",
11
+ "Flag Ch 12: .unwrap() in find panics on missing key — return Option<u32> or Result",
12
+ "Provide corrected versions using &[String], iterators, Result, ?, and &HashMap"
13
+ ]
14
+ },
15
+ {
16
+ "id": "eval-02-subtle-trait-dispatch",
17
+ "prompt": "Review this Rust code:\n\n```rust\nuse std::fmt;\n\ntrait Summarize {\n fn summary(&self) -> String;\n}\n\nstruct Article { title: String, body: String }\nstruct Tweet { content: String }\n\nimpl Summarize for Article {\n fn summary(&self) -> String { self.title.clone() }\n}\nimpl Summarize for Tweet {\n fn summary(&self) -> String { self.content.clone() }\n}\n\n// Returns a trait object\nfn make_summary(is_article: bool) -> Box<dyn Summarize> {\n if is_article {\n Box::new(Article { title: String::from(\"news\"), body: String::from(\"...\") })\n } else {\n Box::new(Tweet { content: String::from(\"hello\") })\n }\n}\n\n// Takes a trait object\nfn print_all(items: &Vec<Box<dyn Summarize>>) {\n for item in items {\n println!(\"{}\", item.summary());\n }\n}\n```",
18
+ "expectations": [
19
+ "Note that Box<dyn Summarize> in make_summary is actually appropriate here because the function must return one of two different concrete types — this is a valid use of dynamic dispatch (Ch 17)",
20
+ "Flag &Vec<Box<dyn Summarize>> in print_all — prefer &[Box<dyn Summarize>] (a slice reference is more general than a Vec reference)",
21
+ "Suggest that if only one type were returned, impl Summarize would be preferred over Box<dyn Summarize> for zero-cost static dispatch (Ch 17)",
22
+ "Note .clone() in summary() implementations — title/body could be returned as &str with lifetime annotation to avoid allocation (Ch 8, 9)",
23
+ "Do NOT flag the dynamic dispatch in make_summary as wrong — it is the correct choice when returning heterogeneous types"
24
+ ]
25
+ },
26
+ {
27
+ "id": "eval-03-idiomatic-rust-already-good",
28
+ "prompt": "Review this Rust code:\n\n```rust\nuse std::fmt;\nuse std::sync::{Arc, Mutex};\n\n#[derive(Debug)]\npub enum StoreError {\n NotFound(String),\n Io(std::io::Error),\n}\n\nimpl fmt::Display for StoreError {\n fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n match self {\n StoreError::NotFound(k) => write!(f, \"key not found: {k}\"),\n StoreError::Io(e) => write!(f, \"io error: {e}\"),\n }\n }\n}\n\nimpl std::error::Error for StoreError {}\n\nimpl From<std::io::Error> for StoreError {\n fn from(e: std::io::Error) -> Self { StoreError::Io(e) }\n}\n\n#[derive(Debug, Clone)]\npub struct Record {\n pub key: String,\n pub value: String,\n}\n\npub struct Store {\n records: Arc<Mutex<Vec<Record>>>,\n}\n\nimpl Store {\n pub fn new() -> Self {\n Store { records: Arc::new(Mutex::new(Vec::new())) }\n }\n\n pub fn insert(&self, key: &str, value: &str) {\n let mut records = self.records.lock().expect(\"mutex poisoned\");\n records.push(Record { key: key.to_string(), value: value.to_string() });\n }\n\n pub fn find(&self, key: &str) -> Result<Record, StoreError> {\n let records = self.records.lock().expect(\"mutex poisoned\");\n records\n .iter()\n .find(|r| r.key == key)\n .cloned()\n .ok_or_else(|| StoreError::NotFound(key.to_string()))\n }\n}\n```",
29
+ "expectations": [
30
+ "Recognize this code is idiomatic Rust — do NOT manufacture issues",
31
+ "Acknowledge: custom StoreError with Display + Error + From (Ch 12), Arc<Mutex<T>> for shared state (Ch 19), &str parameters with .to_string() conversion (Ch 4), iterator .find().cloned() over manual loops (Ch 6), .expect('mutex poisoned') with reason string (Ch 19)",
32
+ "At most note that Default could be derived or implemented for Store alongside new()",
33
+ "Do not flag .cloned() on the found record — cloning a single found item is correct and intentional"
34
+ ]
35
+ }
36
+ ]
37
+ }