@booklib/skills 1.3.2 → 1.4.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/AGENTS.md +108 -0
- package/CLAUDE.md +57 -0
- package/CODE_OF_CONDUCT.md +31 -0
- package/CONTRIBUTING.md +13 -0
- package/README.md +68 -45
- package/SECURITY.md +9 -0
- package/assets/logo.svg +36 -0
- package/demo.gif +0 -0
- package/demo.tape +40 -0
- package/docs/index.html +187 -0
- package/package.json +2 -2
- package/skills/effective-typescript/SKILL.md +166 -0
- package/skills/effective-typescript/evals/evals.json +36 -0
- package/skills/effective-typescript/examples/after.md +70 -0
- package/skills/effective-typescript/examples/before.md +47 -0
- package/skills/effective-typescript/references/api_reference.md +118 -0
- package/skills/effective-typescript/references/practices-catalog.md +371 -0
- package/skills/programming-with-rust/SKILL.md +194 -0
- package/skills/programming-with-rust/evals/evals.json +37 -0
- package/skills/programming-with-rust/examples/after.md +107 -0
- package/skills/programming-with-rust/examples/before.md +59 -0
- package/skills/programming-with-rust/references/api_reference.md +152 -0
- package/skills/programming-with-rust/references/practices-catalog.md +335 -0
- package/skills/rust-in-action/SKILL.md +290 -0
- package/skills/rust-in-action/evals/evals.json +38 -0
- package/skills/rust-in-action/examples/after.md +156 -0
- package/skills/rust-in-action/examples/before.md +56 -0
- package/skills/rust-in-action/references/practices-catalog.md +346 -0
- package/skills/rust-in-action/scripts/review.py +147 -0
- package/skills/skill-router/SKILL.md +16 -13
- package/skills/skill-router/references/skill-catalog.md +19 -1
|
@@ -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
|
+
}
|