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,448 @@
|
|
|
1
|
+
# JavaScript Expert Development Guide
|
|
2
|
+
|
|
3
|
+
Comprehensive guidelines for principal-level JavaScript engineering across all runtimes, frameworks, and paradigms.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This guide applies to:
|
|
10
|
+
- Node.js services, CLIs, and tooling
|
|
11
|
+
- React, Vue, Angular, Svelte, and other UI frameworks
|
|
12
|
+
- Vanilla JavaScript and Web APIs
|
|
13
|
+
- TypeScript (strict mode, always)
|
|
14
|
+
- Build tools, bundlers, and transpilers
|
|
15
|
+
- Testing at every level (unit, integration, E2E, performance)
|
|
16
|
+
|
|
17
|
+
### Core Philosophy
|
|
18
|
+
|
|
19
|
+
JavaScript is the runtime. Know it deeply:
|
|
20
|
+
- The event loop is not optional knowledge — it's the foundation
|
|
21
|
+
- Prototypes, closures, and the module system are first principles
|
|
22
|
+
- The spec (ECMAScript) is the source of truth, not blog posts
|
|
23
|
+
- Every abstraction leaks — understand what's underneath
|
|
24
|
+
|
|
25
|
+
### Key Principles
|
|
26
|
+
|
|
27
|
+
1. **Language Mastery Over Framework Dependence** - Frameworks come and go, JS fundamentals are permanent
|
|
28
|
+
2. **Type Safety Is Non-Negotiable** - TypeScript strict mode, always
|
|
29
|
+
3. **Functional by Default** - Pure functions and composition, classes when lifecycle demands it
|
|
30
|
+
4. **Performance Is a Feature** - Profile before optimizing, measure don't guess
|
|
31
|
+
5. **Error Handling Is Control Flow** - Result types over thrown exceptions
|
|
32
|
+
|
|
33
|
+
### Project Structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
src/
|
|
37
|
+
├── core/ # Pure business logic, zero dependencies
|
|
38
|
+
├── adapters/ # External integrations (DB, HTTP, FS)
|
|
39
|
+
├── services/ # Orchestration layer
|
|
40
|
+
├── utils/ # Pure utility functions
|
|
41
|
+
├── types/ # TypeScript type definitions
|
|
42
|
+
├── __tests__/ # Test files
|
|
43
|
+
└── index.ts # Public API surface
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Language Deep Dive
|
|
49
|
+
|
|
50
|
+
### The Event Loop
|
|
51
|
+
|
|
52
|
+
Understanding execution order is fundamental:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
console.log('1 - sync');
|
|
56
|
+
setTimeout(() => console.log('2 - macrotask'), 0);
|
|
57
|
+
Promise.resolve().then(() => console.log('3 - microtask'));
|
|
58
|
+
queueMicrotask(() => console.log('4 - microtask'));
|
|
59
|
+
console.log('5 - sync');
|
|
60
|
+
// Output: 1, 5, 3, 4, 2
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Type Safety
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Discriminated unions over optional fields
|
|
67
|
+
type Result<T, E = Error> =
|
|
68
|
+
| { ok: true; value: T }
|
|
69
|
+
| { ok: false; error: E };
|
|
70
|
+
|
|
71
|
+
// Branded types for domain safety
|
|
72
|
+
type UserId = string & { readonly __brand: unique symbol };
|
|
73
|
+
|
|
74
|
+
// Const assertions and template literal types
|
|
75
|
+
const EVENTS = ['click', 'keydown', 'submit'] as const;
|
|
76
|
+
type EventName = (typeof EVENTS)[number];
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Functional Patterns
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const pipe = <T>(...fns: Array<(arg: T) => T>) =>
|
|
83
|
+
(value: T): T => fns.reduce((acc, fn) => fn(acc), value);
|
|
84
|
+
|
|
85
|
+
const processUser = pipe(validateInput, normalizeEmail, hashPassword);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Async Patterns
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Parallel independent operations
|
|
92
|
+
const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);
|
|
93
|
+
|
|
94
|
+
// Race with timeout
|
|
95
|
+
const withTimeout = <T>(promise: Promise<T>, ms: number): Promise<T> =>
|
|
96
|
+
Promise.race([
|
|
97
|
+
promise,
|
|
98
|
+
new Promise<never>((_, reject) =>
|
|
99
|
+
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
|
|
100
|
+
),
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
// Async iteration for streams
|
|
104
|
+
async function* readChunks(stream: ReadableStream<Uint8Array>) {
|
|
105
|
+
const reader = stream.getReader();
|
|
106
|
+
try {
|
|
107
|
+
while (true) {
|
|
108
|
+
const { done, value } = await reader.read();
|
|
109
|
+
if (done) break;
|
|
110
|
+
yield value;
|
|
111
|
+
}
|
|
112
|
+
} finally {
|
|
113
|
+
reader.releaseLock();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Generators and Lazy Evaluation
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
function* filter<T>(iterable: Iterable<T>, predicate: (item: T) => boolean) {
|
|
122
|
+
for (const item of iterable) {
|
|
123
|
+
if (predicate(item)) yield item;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function* take<T>(iterable: Iterable<T>, count: number) {
|
|
128
|
+
let i = 0;
|
|
129
|
+
for (const item of iterable) {
|
|
130
|
+
if (i++ >= count) break;
|
|
131
|
+
yield item;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Error Handling
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Result types for expected failures
|
|
140
|
+
function parseConfig(raw: string): Result<Config> {
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(raw);
|
|
143
|
+
const validated = ConfigSchema.safeParse(parsed);
|
|
144
|
+
if (!validated.success) {
|
|
145
|
+
return { ok: false, error: new ValidationError(validated.error) };
|
|
146
|
+
}
|
|
147
|
+
return { ok: true, value: validated.data };
|
|
148
|
+
} catch {
|
|
149
|
+
return { ok: false, error: new ParseError('Invalid JSON') };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Reserve throw for invariant violations
|
|
154
|
+
function assertNonNull<T>(value: T | null | undefined, msg: string): asserts value is T {
|
|
155
|
+
if (value == null) throw new Error(`Invariant: ${msg}`);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Node.js Patterns
|
|
162
|
+
|
|
163
|
+
### Graceful Shutdown
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const shutdown = async (signal: string) => {
|
|
167
|
+
console.log(`Received ${signal}, shutting down...`);
|
|
168
|
+
server.close();
|
|
169
|
+
await db.disconnect();
|
|
170
|
+
process.exit(0);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
174
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Streams
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { pipeline } from 'node:stream/promises';
|
|
181
|
+
|
|
182
|
+
await pipeline(
|
|
183
|
+
createReadStream('input.log'),
|
|
184
|
+
new Transform({
|
|
185
|
+
transform(chunk, _encoding, callback) {
|
|
186
|
+
const filtered = chunk.toString()
|
|
187
|
+
.split('\n')
|
|
188
|
+
.filter((line: string) => line.includes('ERROR'))
|
|
189
|
+
.join('\n');
|
|
190
|
+
callback(null, filtered);
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
createGzip(),
|
|
194
|
+
createWriteStream('errors.log.gz'),
|
|
195
|
+
);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Worker Threads
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
|
|
202
|
+
|
|
203
|
+
if (isMainThread) {
|
|
204
|
+
const runWorker = <T>(data: unknown): Promise<T> =>
|
|
205
|
+
new Promise((resolve, reject) => {
|
|
206
|
+
const worker = new Worker(new URL(import.meta.url), { workerData: data });
|
|
207
|
+
worker.on('message', resolve);
|
|
208
|
+
worker.on('error', reject);
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
const result = performExpensiveComputation(workerData);
|
|
212
|
+
parentPort!.postMessage(result);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Configuration
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { z } from 'zod';
|
|
220
|
+
|
|
221
|
+
const EnvSchema = z.object({
|
|
222
|
+
NODE_ENV: z.enum(['development', 'production', 'test']),
|
|
223
|
+
PORT: z.coerce.number().int().positive().default(3000),
|
|
224
|
+
DATABASE_URL: z.string().url(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export const env = EnvSchema.parse(process.env);
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## React Patterns
|
|
233
|
+
|
|
234
|
+
### Server Components First (React 19+)
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
// Default: Server Component — no 'use client'
|
|
238
|
+
async function ProjectList() {
|
|
239
|
+
const projects = await db.projects.findMany();
|
|
240
|
+
return <ul>{projects.map(p => <ProjectCard key={p.id} project={p} />)}</ul>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Only add 'use client' when you need interactivity
|
|
244
|
+
'use client';
|
|
245
|
+
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
|
|
246
|
+
const [query, setQuery] = useState('');
|
|
247
|
+
return <input value={query} onChange={e => setQuery(e.target.value)} />;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Composition Patterns
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
// Compound components
|
|
255
|
+
function Tabs({ children }: { children: React.ReactNode }) {
|
|
256
|
+
const [active, setActive] = useState(0);
|
|
257
|
+
return <TabsContext.Provider value={{ active, setActive }}>{children}</TabsContext.Provider>;
|
|
258
|
+
}
|
|
259
|
+
Tabs.List = function TabList({ children }) { ... };
|
|
260
|
+
Tabs.Panel = function TabPanel({ children, index }) { ... };
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### State Management Hierarchy
|
|
264
|
+
|
|
265
|
+
1. `useState` — local state
|
|
266
|
+
2. `useReducer` — complex local state with actions
|
|
267
|
+
3. Context — low-frequency shared state (theme, auth, locale)
|
|
268
|
+
4. External store (Zustand, Jotai) — high-frequency shared state
|
|
269
|
+
|
|
270
|
+
### Anti-Patterns
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
// Never: useEffect for derived state
|
|
274
|
+
const fullName = `${firstName} ${lastName}`; // Just compute it
|
|
275
|
+
|
|
276
|
+
// Never: index as key for dynamic lists
|
|
277
|
+
{items.map(item => <Item key={item.id} item={item} />)}
|
|
278
|
+
|
|
279
|
+
// Never: Object literals as default props
|
|
280
|
+
const EMPTY: readonly Item[] = [];
|
|
281
|
+
function List({ items = EMPTY }: { items?: readonly Item[] }) { ... }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Testing
|
|
287
|
+
|
|
288
|
+
### Philosophy
|
|
289
|
+
|
|
290
|
+
- Tests are production code — same quality standards
|
|
291
|
+
- Test behavior and contracts, never implementation details
|
|
292
|
+
- Every bug fix starts with a failing test
|
|
293
|
+
- If it's hard to test, the design is wrong
|
|
294
|
+
|
|
295
|
+
### Unit Tests
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
describe('clamp', () => {
|
|
299
|
+
it('returns value when within range', () => {
|
|
300
|
+
expect(clamp(5, 0, 10)).toBe(5);
|
|
301
|
+
});
|
|
302
|
+
it('returns min when value is below range', () => {
|
|
303
|
+
expect(clamp(-5, 0, 10)).toBe(0);
|
|
304
|
+
});
|
|
305
|
+
it('returns max when value is above range', () => {
|
|
306
|
+
expect(clamp(15, 0, 10)).toBe(10);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Parameterized Tests
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
it.each([
|
|
315
|
+
['Hello World', 'hello-world'],
|
|
316
|
+
[' spaces ', 'spaces'],
|
|
317
|
+
['Special!@#Characters', 'specialcharacters'],
|
|
318
|
+
])('converts "%s" to "%s"', (input, expected) => {
|
|
319
|
+
expect(slugify(input)).toBe(expected);
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Component Tests
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
import { render, screen } from '@testing-library/react';
|
|
327
|
+
import userEvent from '@testing-library/user-event';
|
|
328
|
+
|
|
329
|
+
it('adds a new todo when form is submitted', async () => {
|
|
330
|
+
const user = userEvent.setup();
|
|
331
|
+
render(<TodoList />);
|
|
332
|
+
await user.type(screen.getByRole('textbox', { name: /new todo/i }), 'Buy milk');
|
|
333
|
+
await user.click(screen.getByRole('button', { name: /add/i }));
|
|
334
|
+
expect(screen.getByText('Buy milk')).toBeInTheDocument();
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### API Mocking with MSW
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { http, HttpResponse } from 'msw';
|
|
342
|
+
import { setupServer } from 'msw/node';
|
|
343
|
+
|
|
344
|
+
const server = setupServer(
|
|
345
|
+
http.get('/api/users/:id', ({ params }) => {
|
|
346
|
+
return HttpResponse.json({ id: params.id, name: 'Test User' });
|
|
347
|
+
}),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
|
|
351
|
+
afterEach(() => server.resetHandlers());
|
|
352
|
+
afterAll(() => server.close());
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### E2E Tests
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import { test, expect } from '@playwright/test';
|
|
359
|
+
|
|
360
|
+
test('login flow', async ({ page }) => {
|
|
361
|
+
await page.goto('/login');
|
|
362
|
+
await page.getByLabel('Email').fill('user@example.com');
|
|
363
|
+
await page.getByLabel('Password').fill('password');
|
|
364
|
+
await page.getByRole('button', { name: 'Sign in' }).click();
|
|
365
|
+
await expect(page).toHaveURL('/dashboard');
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Test Anti-Patterns
|
|
370
|
+
|
|
371
|
+
- Never test implementation details (internal state, class names)
|
|
372
|
+
- Never use `setTimeout` for waiting — use `waitFor` or `findBy`
|
|
373
|
+
- Never let tests depend on execution order
|
|
374
|
+
- Never ignore flaky tests — a flaky test is a bug
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Performance
|
|
379
|
+
|
|
380
|
+
### Profiling First
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
performance.mark('operation-start');
|
|
384
|
+
doExpensiveWork();
|
|
385
|
+
performance.mark('operation-end');
|
|
386
|
+
performance.measure('operation', 'operation-start', 'operation-end');
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### V8 Optimization
|
|
390
|
+
|
|
391
|
+
- Keep function argument shapes consistent (monomorphic)
|
|
392
|
+
- Initialize all object properties in the same order
|
|
393
|
+
- Use `Map`/`Set` for dynamic collections, plain objects for static shapes
|
|
394
|
+
- Use `TypedArray` for numeric data
|
|
395
|
+
|
|
396
|
+
### Memory Management
|
|
397
|
+
|
|
398
|
+
- Use `AbortController` signals for event listener cleanup
|
|
399
|
+
- Use `WeakMap`/`WeakRef` for caches that shouldn't prevent GC
|
|
400
|
+
- Avoid closures that capture large scopes — extract only what's needed
|
|
401
|
+
|
|
402
|
+
### Bundle Size
|
|
403
|
+
|
|
404
|
+
- Dynamic imports for code splitting
|
|
405
|
+
- Named exports for tree-shaking
|
|
406
|
+
- Prefer native APIs (`structuredClone`, `Intl`, `URL`, `crypto.randomUUID`)
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Tooling
|
|
411
|
+
|
|
412
|
+
### TypeScript Config
|
|
413
|
+
|
|
414
|
+
```jsonc
|
|
415
|
+
{
|
|
416
|
+
"compilerOptions": {
|
|
417
|
+
"strict": true,
|
|
418
|
+
"noUncheckedIndexedAccess": true,
|
|
419
|
+
"exactOptionalPropertyTypes": true,
|
|
420
|
+
"verbatimModuleSyntax": true,
|
|
421
|
+
"module": "ESNext",
|
|
422
|
+
"target": "ES2022"
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Dependency Hygiene
|
|
428
|
+
|
|
429
|
+
- Pin exact versions for applications
|
|
430
|
+
- Audit regularly (`npm audit`)
|
|
431
|
+
- Prefer zero-dependency packages
|
|
432
|
+
- Check bundle size before adding (`bundlephobia.com`)
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Definition of Done
|
|
437
|
+
|
|
438
|
+
A JavaScript feature is complete when:
|
|
439
|
+
|
|
440
|
+
- [ ] TypeScript strict mode passes with zero errors
|
|
441
|
+
- [ ] All code paths have explicit types (no `any`)
|
|
442
|
+
- [ ] Unit tests cover logic branches and edge cases
|
|
443
|
+
- [ ] Integration tests verify external boundaries
|
|
444
|
+
- [ ] Error cases are tested, not just happy paths
|
|
445
|
+
- [ ] No floating promises
|
|
446
|
+
- [ ] No memory leaks in long-running paths
|
|
447
|
+
- [ ] Bundle impact assessed (client-side code)
|
|
448
|
+
- [ ] Code reviewed and approved
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Rust Concurrency
|
|
2
|
+
|
|
3
|
+
Rust prevents data races at compile time through ownership and the `Send`/`Sync` marker traits. Fearless concurrency is real — but it doesn't mean you can ignore deadlocks, race conditions at the logic level, or performance pitfalls.
|
|
4
|
+
|
|
5
|
+
## Send and Sync
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
// Send: safe to transfer ownership to another thread
|
|
9
|
+
// Sync: safe to share references (&T) between threads
|
|
10
|
+
// These are auto-traits — the compiler derives them automatically
|
|
11
|
+
|
|
12
|
+
// Most types are Send + Sync
|
|
13
|
+
// Rc<T> is NOT Send or Sync — use Arc<T> for multi-threaded code
|
|
14
|
+
// RefCell<T> is Send but NOT Sync — use Mutex<T> for multi-threaded code
|
|
15
|
+
// Raw pointers are neither — wrap them in types that uphold invariants
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Threading
|
|
19
|
+
|
|
20
|
+
### std::thread
|
|
21
|
+
|
|
22
|
+
```rust
|
|
23
|
+
use std::thread;
|
|
24
|
+
|
|
25
|
+
// Scoped threads (Go 1.63+) — borrows from the parent stack are allowed
|
|
26
|
+
let mut data = vec![1, 2, 3, 4];
|
|
27
|
+
thread::scope(|s| {
|
|
28
|
+
let (left, right) = data.split_at_mut(2);
|
|
29
|
+
|
|
30
|
+
s.spawn(|| {
|
|
31
|
+
left.iter_mut().for_each(|x| *x *= 2);
|
|
32
|
+
});
|
|
33
|
+
s.spawn(|| {
|
|
34
|
+
right.iter_mut().for_each(|x| *x *= 3);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
// Both threads are guaranteed to finish before scope exits
|
|
38
|
+
// No Arc, no clone, no join handles to manage
|
|
39
|
+
assert_eq!(data, [2, 4, 9, 12]);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Shared State with Arc + Mutex
|
|
43
|
+
|
|
44
|
+
```rust
|
|
45
|
+
use std::sync::{Arc, Mutex};
|
|
46
|
+
|
|
47
|
+
let counter = Arc::new(Mutex::new(0));
|
|
48
|
+
let mut handles = vec![];
|
|
49
|
+
|
|
50
|
+
for _ in 0..10 {
|
|
51
|
+
let counter = Arc::clone(&counter);
|
|
52
|
+
handles.push(thread::spawn(move || {
|
|
53
|
+
let mut num = counter.lock().unwrap();
|
|
54
|
+
*num += 1;
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for handle in handles {
|
|
59
|
+
handle.join().unwrap();
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### RwLock for Read-Heavy Workloads
|
|
64
|
+
|
|
65
|
+
```rust
|
|
66
|
+
use std::sync::RwLock;
|
|
67
|
+
|
|
68
|
+
let config = Arc::new(RwLock::new(Config::default()));
|
|
69
|
+
|
|
70
|
+
// Multiple readers — no blocking
|
|
71
|
+
let cfg = config.read().unwrap();
|
|
72
|
+
println!("{}", cfg.setting);
|
|
73
|
+
|
|
74
|
+
// Single writer — exclusive access
|
|
75
|
+
let mut cfg = config.write().unwrap();
|
|
76
|
+
cfg.setting = new_value;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Channels
|
|
80
|
+
|
|
81
|
+
```rust
|
|
82
|
+
use std::sync::mpsc;
|
|
83
|
+
|
|
84
|
+
// Multiple producers, single consumer
|
|
85
|
+
let (tx, rx) = mpsc::channel();
|
|
86
|
+
|
|
87
|
+
let tx2 = tx.clone(); // Clone sender for second producer
|
|
88
|
+
|
|
89
|
+
thread::spawn(move || {
|
|
90
|
+
tx.send(Message::Data(vec![1, 2, 3])).unwrap();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
thread::spawn(move || {
|
|
94
|
+
tx2.send(Message::Data(vec![4, 5, 6])).unwrap();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Receive all messages
|
|
98
|
+
for msg in rx {
|
|
99
|
+
process(msg);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// crossbeam-channel for multi-producer multi-consumer, select!, bounded channels
|
|
103
|
+
use crossbeam_channel::{bounded, select};
|
|
104
|
+
|
|
105
|
+
let (tx, rx) = bounded(100); // Backpressure at 100 items
|
|
106
|
+
|
|
107
|
+
select! {
|
|
108
|
+
recv(rx) -> msg => handle(msg.unwrap()),
|
|
109
|
+
default(Duration::from_secs(1)) => println!("timeout"),
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Async/Await
|
|
114
|
+
|
|
115
|
+
### Tokio Runtime
|
|
116
|
+
|
|
117
|
+
```rust
|
|
118
|
+
#[tokio::main]
|
|
119
|
+
async fn main() -> Result<()> {
|
|
120
|
+
let listener = TcpListener::bind("0.0.0.0:8080").await?;
|
|
121
|
+
|
|
122
|
+
loop {
|
|
123
|
+
let (stream, addr) = listener.accept().await?;
|
|
124
|
+
tokio::spawn(async move {
|
|
125
|
+
if let Err(e) = handle_connection(stream).await {
|
|
126
|
+
eprintln!("connection error from {addr}: {e}");
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Async Patterns
|
|
134
|
+
|
|
135
|
+
```rust
|
|
136
|
+
// Concurrent execution with join
|
|
137
|
+
let (users, posts) = tokio::join!(
|
|
138
|
+
fetch_users(&client),
|
|
139
|
+
fetch_posts(&client),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Race — first to complete wins
|
|
143
|
+
tokio::select! {
|
|
144
|
+
result = fetch_data() => handle_data(result),
|
|
145
|
+
_ = tokio::time::sleep(Duration::from_secs(5)) => {
|
|
146
|
+
return Err(anyhow!("fetch timed out"));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Bounded concurrency with semaphore
|
|
151
|
+
use tokio::sync::Semaphore;
|
|
152
|
+
|
|
153
|
+
let semaphore = Arc::new(Semaphore::new(10));
|
|
154
|
+
let mut handles = vec![];
|
|
155
|
+
|
|
156
|
+
for url in urls {
|
|
157
|
+
let permit = semaphore.clone().acquire_owned().await?;
|
|
158
|
+
handles.push(tokio::spawn(async move {
|
|
159
|
+
let result = fetch(url).await;
|
|
160
|
+
drop(permit); // Release when done
|
|
161
|
+
result
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Stream processing
|
|
166
|
+
use tokio_stream::StreamExt;
|
|
167
|
+
|
|
168
|
+
let mut stream = tokio_stream::iter(items)
|
|
169
|
+
.map(|item| async move { process(item).await })
|
|
170
|
+
.buffer_unordered(10); // Process up to 10 concurrently
|
|
171
|
+
|
|
172
|
+
while let Some(result) = stream.next().await {
|
|
173
|
+
handle(result?);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Async Traits
|
|
178
|
+
|
|
179
|
+
```rust
|
|
180
|
+
// Since Rust 1.75: async fn in traits works natively
|
|
181
|
+
pub trait Repository {
|
|
182
|
+
async fn find_by_id(&self, id: &str) -> Result<Option<Entity>>;
|
|
183
|
+
async fn save(&self, entity: &Entity) -> Result<()>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// For trait objects (dyn), use the async-trait crate or
|
|
187
|
+
// return Pin<Box<dyn Future>> manually
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Cancellation Safety
|
|
191
|
+
|
|
192
|
+
```rust
|
|
193
|
+
// tokio::select! can cancel futures — understand what that means
|
|
194
|
+
// A future dropped mid-execution won't run its remaining code
|
|
195
|
+
|
|
196
|
+
// Cancellation-safe: reading from a channel (no partial state)
|
|
197
|
+
// NOT cancellation-safe: reading into a buffer (partial read lost)
|
|
198
|
+
|
|
199
|
+
tokio::select! {
|
|
200
|
+
// Safe: mpsc::Receiver::recv is cancellation-safe
|
|
201
|
+
msg = rx.recv() => { ... }
|
|
202
|
+
|
|
203
|
+
// DANGEROUS if buf has partial state from a previous iteration
|
|
204
|
+
n = reader.read(&mut buf) => { ... }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// When in doubt, consult tokio's docs for each method's cancellation safety
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Atomics
|
|
211
|
+
|
|
212
|
+
```rust
|
|
213
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
214
|
+
|
|
215
|
+
static REQUEST_COUNT: AtomicU64 = AtomicU64::new(0);
|
|
216
|
+
|
|
217
|
+
fn handle_request() {
|
|
218
|
+
REQUEST_COUNT.fetch_add(1, Ordering::Relaxed);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Ordering guide:
|
|
222
|
+
// Relaxed — no ordering guarantees, just atomicity (counters)
|
|
223
|
+
// Acquire — subsequent reads see writes before the Release
|
|
224
|
+
// Release — previous writes are visible to Acquire loads
|
|
225
|
+
// SeqCst — total order, strongest guarantee, rarely needed
|
|
226
|
+
// If unsure, use SeqCst and optimize later with profiling data
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Anti-Patterns
|
|
230
|
+
|
|
231
|
+
```rust
|
|
232
|
+
// Never: Holding a MutexGuard across an await point
|
|
233
|
+
let guard = mutex.lock().await;
|
|
234
|
+
do_async_work().await; // Deadlock risk — guard is held across await
|
|
235
|
+
drop(guard);
|
|
236
|
+
// Instead: lock, extract data, drop guard, then await
|
|
237
|
+
|
|
238
|
+
// Never: Spawning tasks without cancellation or shutdown strategy
|
|
239
|
+
tokio::spawn(async { loop { do_work().await; } }); // How does this stop?
|
|
240
|
+
|
|
241
|
+
// Never: Blocking in async context
|
|
242
|
+
std::thread::sleep(Duration::from_secs(1)); // Blocks the executor thread!
|
|
243
|
+
tokio::time::sleep(Duration::from_secs(1)).await; // Use this instead
|
|
244
|
+
|
|
245
|
+
// For CPU-bound work in async context:
|
|
246
|
+
tokio::task::spawn_blocking(|| expensive_computation()).await?;
|
|
247
|
+
|
|
248
|
+
// Never: Using std::sync::Mutex in async code (blocks the executor)
|
|
249
|
+
// Use tokio::sync::Mutex instead when you must hold across awaits
|
|
250
|
+
```
|