@pilllesss/yorn 1.0.182 → 1.0.183

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.

Potentially problematic release.


This version of @pilllesss/yorn might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/providers/data/.manifest.json +1 -1
  3. package/dist/skills/code-review/LICENSE +21 -0
  4. package/dist/skills/code-review/SKILL.md +233 -0
  5. package/dist/skills/code-review/assets/pr-review-template.md +137 -0
  6. package/dist/skills/code-review/assets/review-checklist.md +123 -0
  7. package/dist/skills/code-review/reference/angular.md +768 -0
  8. package/dist/skills/code-review/reference/architecture-review-guide.md +472 -0
  9. package/dist/skills/code-review/reference/c.md +890 -0
  10. package/dist/skills/code-review/reference/code-quality-universal.md +488 -0
  11. package/dist/skills/code-review/reference/code-review-best-practices.md +136 -0
  12. package/dist/skills/code-review/reference/common-bugs-checklist.md +302 -0
  13. package/dist/skills/code-review/reference/cpp.md +893 -0
  14. package/dist/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  15. package/dist/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
  16. package/dist/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
  17. package/dist/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
  18. package/dist/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
  19. package/dist/skills/code-review/reference/csharp.md +525 -0
  20. package/dist/skills/code-review/reference/css-less-sass.md +661 -0
  21. package/dist/skills/code-review/reference/dart.md +670 -0
  22. package/dist/skills/code-review/reference/django.md +985 -0
  23. package/dist/skills/code-review/reference/fastapi.md +580 -0
  24. package/dist/skills/code-review/reference/go.md +993 -0
  25. package/dist/skills/code-review/reference/java.md +409 -0
  26. package/dist/skills/code-review/reference/java8.md +586 -0
  27. package/dist/skills/code-review/reference/kotlin.md +1018 -0
  28. package/dist/skills/code-review/reference/nestjs.md +593 -0
  29. package/dist/skills/code-review/reference/performance-review-guide.md +816 -0
  30. package/dist/skills/code-review/reference/php.md +684 -0
  31. package/dist/skills/code-review/reference/python.md +1073 -0
  32. package/dist/skills/code-review/reference/qt.md +757 -0
  33. package/dist/skills/code-review/reference/react.md +871 -0
  34. package/dist/skills/code-review/reference/ruby.md +964 -0
  35. package/dist/skills/code-review/reference/rust.md +846 -0
  36. package/dist/skills/code-review/reference/security-review-guide.md +494 -0
  37. package/dist/skills/code-review/reference/svelte.md +1064 -0
  38. package/dist/skills/code-review/reference/swift.md +936 -0
  39. package/dist/skills/code-review/reference/typescript.md +1016 -0
  40. package/dist/skills/code-review/reference/vue.md +924 -0
  41. package/dist/skills/code-review/reference/zig.md +440 -0
  42. package/dist/skills/code-review/scripts/pr-analyzer.py +435 -0
  43. package/dist/skills/code-review/scripts/test_pr_analyzer.py +380 -0
  44. package/dist/yorn.cjs +628 -628
  45. package/package.json +2 -2
@@ -0,0 +1,846 @@
1
+ # Rust Code Review Guide
2
+
3
+ > Rust 代码审查指南。编译器能捕获内存安全问题,但审查者需要关注编译器无法检测的问题——业务逻辑、API 设计、性能、取消安全性和可维护性。
4
+
5
+ ## 目录
6
+
7
+ - [所有权与借用](#所有权与借用)
8
+ - [Unsafe 代码审查](#unsafe-代码审查最关键)
9
+ - [异步代码](#异步代码)
10
+ - [取消安全性](#取消安全性)
11
+ - [spawn vs await](#spawn-vs-await)
12
+ - [错误处理](#错误处理)
13
+ - [性能](#性能)
14
+ - [Trait 设计](#trait-设计)
15
+ - [Review Checklist](#rust-review-checklist)
16
+
17
+ ---
18
+
19
+ ## 所有权与借用
20
+
21
+ ### 避免不必要的 clone()
22
+
23
+ ```rust
24
+ // ❌ clone() 是"Rust 的胶带"——用于绕过借用检查器
25
+ fn bad_process(data: &Data) -> Result<()> {
26
+ let owned = data.clone(); // 为什么需要 clone?
27
+ expensive_operation(owned)
28
+ }
29
+
30
+ // ✅ 审查时问:clone 是否必要?能否用借用?
31
+ fn good_process(data: &Data) -> Result<()> {
32
+ expensive_operation(data) // 传递引用
33
+ }
34
+
35
+ // ✅ 如果确实需要 clone,添加注释说明原因
36
+ fn justified_clone(data: &Data) -> Result<()> {
37
+ // Clone needed: data will be moved to spawned task
38
+ let owned = data.clone();
39
+ tokio::spawn(async move {
40
+ process(owned).await
41
+ });
42
+ Ok(())
43
+ }
44
+ ```
45
+
46
+ ### Arc<Mutex<T>> 的使用
47
+
48
+ ```rust
49
+ // ❌ Arc<Mutex<T>> 可能隐藏不必要的共享状态
50
+ struct BadService {
51
+ cache: Arc<Mutex<HashMap<String, Data>>>, // 真的需要共享?
52
+ }
53
+
54
+ // ✅ 考虑是否需要共享,或者设计可以避免
55
+ struct GoodService {
56
+ cache: HashMap<String, Data>, // 单一所有者
57
+ }
58
+
59
+ // ✅ 如果确实需要并发访问,考虑更好的数据结构
60
+ use dashmap::DashMap;
61
+
62
+ struct ConcurrentService {
63
+ cache: DashMap<String, Data>, // 更细粒度的锁
64
+ }
65
+ ```
66
+
67
+ ### Cow (Copy-on-Write) 模式
68
+
69
+ ```rust
70
+ use std::borrow::Cow;
71
+
72
+ // ❌ 总是分配新字符串
73
+ fn bad_process_name(name: &str) -> String {
74
+ if name.is_empty() {
75
+ "Unknown".to_string() // 分配
76
+ } else {
77
+ name.to_string() // 不必要的分配
78
+ }
79
+ }
80
+
81
+ // ✅ 使用 Cow 避免不必要的分配
82
+ fn good_process_name(name: &str) -> Cow<'_, str> {
83
+ if name.is_empty() {
84
+ Cow::Borrowed("Unknown") // 静态字符串,无分配
85
+ } else {
86
+ Cow::Borrowed(name) // 借用原始数据
87
+ }
88
+ }
89
+
90
+ // ✅ 只在需要修改时才分配
91
+ fn normalize_name(name: &str) -> Cow<'_, str> {
92
+ if name.chars().any(|c| c.is_uppercase()) {
93
+ Cow::Owned(name.to_lowercase()) // 需要修改,分配
94
+ } else {
95
+ Cow::Borrowed(name) // 无需修改,借用
96
+ }
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Unsafe 代码审查(最关键!)
103
+
104
+ ### 基本要求
105
+
106
+ ```rust
107
+ // ❌ unsafe 没有安全文档——这是红旗
108
+ unsafe fn bad_transmute<T, U>(t: T) -> U {
109
+ std::mem::transmute(t)
110
+ }
111
+
112
+ // ✅ 每个 unsafe 必须解释:为什么安全?什么不变量?
113
+ /// Transmutes `T` to `U`.
114
+ ///
115
+ /// # Safety
116
+ ///
117
+ /// - `T` and `U` must have the same size and alignment
118
+ /// - `T` must be a valid bit pattern for `U`
119
+ /// - The caller ensures no references to `t` exist after this call
120
+ unsafe fn documented_transmute<T, U>(t: T) -> U {
121
+ // SAFETY: Caller guarantees size/alignment match and bit validity
122
+ std::mem::transmute(t)
123
+ }
124
+ ```
125
+
126
+ ### Unsafe 块注释
127
+
128
+ ```rust
129
+ // ❌ 没有解释的 unsafe 块
130
+ fn bad_get_unchecked(slice: &[u8], index: usize) -> u8 {
131
+ unsafe { *slice.get_unchecked(index) }
132
+ }
133
+
134
+ // ✅ 每个 unsafe 块必须有 SAFETY 注释
135
+ fn good_get_unchecked(slice: &[u8], index: usize) -> u8 {
136
+ debug_assert!(index < slice.len(), "index out of bounds");
137
+ // SAFETY: We verified index < slice.len() via debug_assert.
138
+ // In release builds, callers must ensure valid index.
139
+ unsafe { *slice.get_unchecked(index) }
140
+ }
141
+
142
+ // ✅ 封装 unsafe 提供安全 API
143
+ pub fn checked_get(slice: &[u8], index: usize) -> Option<u8> {
144
+ if index < slice.len() {
145
+ // SAFETY: bounds check performed above
146
+ Some(unsafe { *slice.get_unchecked(index) })
147
+ } else {
148
+ None
149
+ }
150
+ }
151
+ ```
152
+
153
+ ### 常见 unsafe 模式
154
+
155
+ ```rust
156
+ // ✅ FFI 边界
157
+ extern "C" {
158
+ fn external_function(ptr: *const u8, len: usize) -> i32;
159
+ }
160
+
161
+ pub fn safe_wrapper(data: &[u8]) -> Result<i32, Error> {
162
+ // SAFETY: data.as_ptr() is valid for data.len() bytes,
163
+ // and external_function only reads from the buffer.
164
+ let result = unsafe {
165
+ external_function(data.as_ptr(), data.len())
166
+ };
167
+ if result < 0 {
168
+ Err(Error::from_code(result))
169
+ } else {
170
+ Ok(result)
171
+ }
172
+ }
173
+
174
+ // ✅ 性能关键路径的 unsafe
175
+ pub fn fast_copy(src: &[u8], dst: &mut [u8]) {
176
+ assert_eq!(src.len(), dst.len(), "slices must be equal length");
177
+ // SAFETY: src and dst are valid slices of equal length,
178
+ // and dst is mutable so no aliasing.
179
+ unsafe {
180
+ std::ptr::copy_nonoverlapping(
181
+ src.as_ptr(),
182
+ dst.as_mut_ptr(),
183
+ src.len()
184
+ );
185
+ }
186
+ }
187
+ ```
188
+
189
+ ---
190
+
191
+ ## 异步代码
192
+
193
+ > 📖 通用并发模式和跨语言示例详见 [异步与并发跨语言指南](cross-cutting/async-concurrency-patterns.md)
194
+
195
+ ### 避免阻塞操作
196
+
197
+ ```rust
198
+ // ❌ 在 async 上下文中阻塞——会饿死其他任务
199
+ async fn bad_async() {
200
+ let data = std::fs::read_to_string("file.txt").unwrap(); // 阻塞!
201
+ std::thread::sleep(Duration::from_secs(1)); // 阻塞!
202
+ }
203
+
204
+ // ✅ 使用异步 API
205
+ async fn good_async() -> Result<String> {
206
+ let data = tokio::fs::read_to_string("file.txt").await?;
207
+ tokio::time::sleep(Duration::from_secs(1)).await;
208
+ Ok(data)
209
+ }
210
+
211
+ // ✅ 如果必须使用阻塞操作,用 spawn_blocking
212
+ async fn with_blocking() -> Result<Data> {
213
+ let result = tokio::task::spawn_blocking(|| {
214
+ // 这里可以安全地进行阻塞操作
215
+ expensive_cpu_computation()
216
+ }).await?;
217
+ Ok(result)
218
+ }
219
+ ```
220
+
221
+ ### Mutex 和 .await
222
+
223
+ ```rust
224
+ // ❌ 跨 .await 持有 std::sync::Mutex——可能死锁
225
+ async fn bad_lock(mutex: &std::sync::Mutex<Data>) {
226
+ let guard = mutex.lock().unwrap();
227
+ async_operation().await; // 持锁等待!
228
+ process(&guard);
229
+ }
230
+
231
+ // ✅ 方案1:最小化锁范围
232
+ async fn good_lock_scoped(mutex: &std::sync::Mutex<Data>) {
233
+ let data = {
234
+ let guard = mutex.lock().unwrap();
235
+ guard.clone() // 立即释放锁
236
+ };
237
+ async_operation().await;
238
+ process(&data);
239
+ }
240
+
241
+ // ✅ 方案2:使用 tokio::sync::Mutex(可跨 await)
242
+ async fn good_lock_tokio(mutex: &tokio::sync::Mutex<Data>) {
243
+ let guard = mutex.lock().await;
244
+ async_operation().await; // OK: tokio Mutex 设计为可跨 await
245
+ process(&guard);
246
+ }
247
+
248
+ // 💡 选择指南:
249
+ // - std::sync::Mutex:低竞争、短临界区、不跨 await
250
+ // - tokio::sync::Mutex:需要跨 await、高竞争场景
251
+ ```
252
+
253
+ ### 异步 trait 方法
254
+
255
+ ```rust
256
+ // ❌ async trait 方法的陷阱(旧版本)
257
+ #[async_trait]
258
+ trait BadRepository {
259
+ async fn find(&self, id: i64) -> Option<Entity>; // 隐式 Box
260
+ }
261
+
262
+ // ✅ Rust 1.75+:原生 async trait 方法
263
+ trait Repository {
264
+ async fn find(&self, id: i64) -> Option<Entity>;
265
+
266
+ // 返回具体 Future 类型以避免 allocation
267
+ fn find_many(&self, ids: &[i64]) -> impl Future<Output = Vec<Entity>> + Send;
268
+ }
269
+
270
+ // ✅ 对于需要 dyn 的场景
271
+ trait DynRepository: Send + Sync {
272
+ fn find(&self, id: i64) -> Pin<Box<dyn Future<Output = Option<Entity>> + Send + '_>>;
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ## 取消安全性
279
+
280
+ ### 什么是取消安全
281
+
282
+ ```rust
283
+ // 当一个 Future 在 .await 点被 drop 时,它处于什么状态?
284
+ // 取消安全的 Future:可以在任何 await 点安全取消
285
+ // 取消不安全的 Future:取消可能导致数据丢失或不一致状态
286
+
287
+ // ❌ 取消不安全的例子
288
+ async fn cancel_unsafe(conn: &mut Connection) -> Result<()> {
289
+ let data = receive_data().await; // 如果这里被取消...
290
+ conn.send_ack().await; // ...确认永远不会发送,数据可能丢失
291
+ Ok(())
292
+ }
293
+
294
+ // ✅ 取消安全的版本
295
+ async fn cancel_safe(conn: &mut Connection) -> Result<()> {
296
+ // 使用事务或原子操作确保一致性
297
+ let transaction = conn.begin_transaction().await?;
298
+ let data = receive_data().await;
299
+ transaction.commit_with_ack(data).await?; // 原子操作
300
+ Ok(())
301
+ }
302
+ ```
303
+
304
+ ### select! 中的取消安全
305
+
306
+ ```rust
307
+ use tokio::select;
308
+
309
+ // ❌ 在 select! 中使用取消不安全的 Future
310
+ async fn bad_select(stream: &mut TcpStream) {
311
+ let mut buffer = vec![0u8; 1024];
312
+ loop {
313
+ select! {
314
+ // read_exact 不是取消安全的:timeout 先完成时,
315
+ // 已经读进 buffer 的部分字节会随 Future 一起丢弃
316
+ result = stream.read_exact(&mut buffer) => {
317
+ result?;
318
+ handle_data(&buffer);
319
+ }
320
+ _ = tokio::time::sleep(Duration::from_secs(5)) => {
321
+ println!("Timeout");
322
+ }
323
+ }
324
+ }
325
+ }
326
+
327
+ // ✅ 使用取消安全的 API
328
+ async fn good_select(stream: &mut TcpStream) {
329
+ let mut buffer = vec![0u8; 1024];
330
+ loop {
331
+ select! {
332
+ // read 是取消安全的:被取消时未读取的数据仍留在流中
333
+ // 真的需要按定长读取时,把 read_exact 丢到单独的 task 里,
334
+ // 这里 select! 它的 JoinHandle,取消就不会丢字节
335
+ result = stream.read(&mut buffer) => {
336
+ match result {
337
+ Ok(0) => break, // EOF
338
+ Ok(n) => handle_data(&buffer[..n]),
339
+ Err(e) => return Err(e),
340
+ }
341
+ }
342
+ _ = tokio::time::sleep(Duration::from_secs(5)) => {
343
+ println!("Timeout, retrying...");
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ // ✅ 使用 tokio::pin! 确保 Future 可以安全重用
350
+ async fn pinned_select() {
351
+ let sleep = tokio::time::sleep(Duration::from_secs(10));
352
+ tokio::pin!(sleep);
353
+
354
+ loop {
355
+ select! {
356
+ _ = &mut sleep => {
357
+ println!("Timer elapsed");
358
+ break;
359
+ }
360
+ data = receive_data() => {
361
+ process(data).await;
362
+ // sleep 继续倒计时,不会重置
363
+ }
364
+ }
365
+ }
366
+ }
367
+ ```
368
+
369
+ ### 文档化取消安全性
370
+
371
+ ```rust
372
+ /// Reads a complete message from the stream.
373
+ ///
374
+ /// # Cancel Safety
375
+ ///
376
+ /// This method is **not** cancel safe. If cancelled while reading,
377
+ /// partial data may be lost and the stream state becomes undefined.
378
+ /// Use `read_message_cancel_safe` if cancellation is expected.
379
+ async fn read_message(stream: &mut TcpStream) -> Result<Message> {
380
+ let len = stream.read_u32().await?;
381
+ let mut buffer = vec![0u8; len as usize];
382
+ stream.read_exact(&mut buffer).await?;
383
+ Ok(Message::from_bytes(&buffer))
384
+ }
385
+
386
+ /// Reads a message with cancel safety.
387
+ ///
388
+ /// # Cancel Safety
389
+ ///
390
+ /// This method is cancel safe. If cancelled, any partial data
391
+ /// is preserved in the internal buffer for the next call.
392
+ async fn read_message_cancel_safe(reader: &mut BufferedReader) -> Result<Message> {
393
+ reader.read_message_buffered().await
394
+ }
395
+ ```
396
+
397
+ ---
398
+
399
+ ## spawn vs await
400
+
401
+ ### 何时使用 spawn
402
+
403
+ ```rust
404
+ // ❌ 不必要的 spawn——增加开销,失去结构化并发
405
+ async fn bad_unnecessary_spawn() {
406
+ let handle = tokio::spawn(async {
407
+ simple_operation().await
408
+ });
409
+ handle.await.unwrap(); // 为什么不直接 await?
410
+ }
411
+
412
+ // ✅ 直接 await 简单操作
413
+ async fn good_direct_await() {
414
+ simple_operation().await;
415
+ }
416
+
417
+ // ✅ spawn 用于真正的并行执行
418
+ async fn good_parallel_spawn() {
419
+ let task1 = tokio::spawn(fetch_from_service_a());
420
+ let task2 = tokio::spawn(fetch_from_service_b());
421
+
422
+ // 两个请求并行执行
423
+ let (result1, result2) = tokio::try_join!(task1, task2)?;
424
+ }
425
+
426
+ // ✅ spawn 用于后台任务(fire-and-forget)
427
+ async fn good_background_spawn() {
428
+ // 启动后台任务,不等待完成
429
+ tokio::spawn(async {
430
+ cleanup_old_sessions().await;
431
+ log_metrics().await;
432
+ });
433
+
434
+ // 继续执行其他工作
435
+ handle_request().await;
436
+ }
437
+ ```
438
+
439
+ ### spawn 的 'static 要求
440
+
441
+ ```rust
442
+ // ❌ spawn 的 Future 必须是 'static
443
+ async fn bad_spawn_borrow(data: &Data) {
444
+ tokio::spawn(async {
445
+ process(data).await; // Error: `data` 不是 'static
446
+ });
447
+ }
448
+
449
+ // ✅ 方案1:克隆数据
450
+ async fn good_spawn_clone(data: &Data) {
451
+ let owned = data.clone();
452
+ tokio::spawn(async move {
453
+ process(&owned).await;
454
+ });
455
+ }
456
+
457
+ // ✅ 方案2:使用 Arc 共享
458
+ async fn good_spawn_arc(data: Arc<Data>) {
459
+ let data = Arc::clone(&data);
460
+ tokio::spawn(async move {
461
+ process(&data).await;
462
+ });
463
+ }
464
+
465
+ // ✅ 方案3:使用作用域任务(tokio-scoped 或 async-scoped)
466
+ async fn good_scoped_spawn(data: &Data) {
467
+ // 假设使用 async-scoped crate
468
+ async_scoped::scope(|s| async {
469
+ s.spawn(async {
470
+ process(data).await; // 可以借用
471
+ });
472
+ }).await;
473
+ }
474
+ ```
475
+
476
+ ### JoinHandle 错误处理
477
+
478
+ ```rust
479
+ // ❌ 忽略 spawn 的错误
480
+ async fn bad_ignore_spawn_error() {
481
+ let handle = tokio::spawn(async {
482
+ risky_operation().await
483
+ });
484
+ let _ = handle.await; // 忽略了 panic 和错误
485
+ }
486
+
487
+ // ✅ 正确处理 JoinHandle 结果
488
+ async fn good_handle_spawn_error() -> Result<()> {
489
+ let handle = tokio::spawn(async {
490
+ risky_operation().await
491
+ });
492
+
493
+ match handle.await {
494
+ Ok(Ok(result)) => {
495
+ // 任务成功完成
496
+ process_result(result);
497
+ Ok(())
498
+ }
499
+ Ok(Err(e)) => {
500
+ // 任务内部错误
501
+ Err(e.into())
502
+ }
503
+ Err(join_err) => {
504
+ // 任务 panic 或被取消
505
+ if join_err.is_panic() {
506
+ error!("Task panicked: {:?}", join_err);
507
+ }
508
+ Err(anyhow!("Task failed: {}", join_err))
509
+ }
510
+ }
511
+ }
512
+ ```
513
+
514
+ ### 结构化并发 vs spawn
515
+
516
+ ```rust
517
+ // ✅ 优先使用 join!(结构化并发)
518
+ async fn structured_concurrency() -> Result<(A, B, C)> {
519
+ // 所有任务在同一个作用域内
520
+ // 如果任何一个失败,其他的会被取消
521
+ tokio::try_join!(
522
+ fetch_a(),
523
+ fetch_b(),
524
+ fetch_c()
525
+ )
526
+ }
527
+
528
+ // ✅ 使用 spawn 时考虑任务生命周期
529
+ struct TaskManager {
530
+ handles: Vec<JoinHandle<()>>,
531
+ }
532
+
533
+ impl TaskManager {
534
+ async fn shutdown(self) {
535
+ // 优雅关闭:等待所有任务完成
536
+ for handle in self.handles {
537
+ if let Err(e) = handle.await {
538
+ error!("Task failed during shutdown: {}", e);
539
+ }
540
+ }
541
+ }
542
+
543
+ async fn abort_all(self) {
544
+ // 强制关闭:取消所有任务
545
+ for handle in self.handles {
546
+ handle.abort();
547
+ }
548
+ }
549
+ }
550
+ ```
551
+
552
+ ---
553
+
554
+ ## 错误处理
555
+
556
+ > 📖 通用原则和跨语言示例详见 [错误处理跨语言指南](cross-cutting/error-handling-principles.md)
557
+
558
+ ### 库 vs 应用的错误类型
559
+
560
+ ```rust
561
+ // ❌ 库代码用 anyhow——调用者无法 match 错误
562
+ pub fn parse_config(s: &str) -> anyhow::Result<Config> { ... }
563
+
564
+ // ✅ 库用 thiserror,应用用 anyhow
565
+ #[derive(Debug, thiserror::Error)]
566
+ pub enum ConfigError {
567
+ #[error("invalid syntax at line {line}: {message}")]
568
+ Syntax { line: usize, message: String },
569
+ #[error("missing required field: {0}")]
570
+ MissingField(String),
571
+ #[error(transparent)]
572
+ Io(#[from] std::io::Error),
573
+ }
574
+
575
+ pub fn parse_config(s: &str) -> Result<Config, ConfigError> { ... }
576
+ ```
577
+
578
+ ### 保留错误上下文
579
+
580
+ ```rust
581
+ // ❌ 吞掉错误上下文
582
+ fn bad_error() -> Result<()> {
583
+ operation().map_err(|_| anyhow!("failed"))?; // 原始错误丢失
584
+ Ok(())
585
+ }
586
+
587
+ // ✅ 使用 context 保留错误链
588
+ fn good_error() -> Result<()> {
589
+ operation().context("failed to perform operation")?;
590
+ Ok(())
591
+ }
592
+
593
+ // ✅ 使用 with_context 进行懒计算
594
+ fn good_error_lazy() -> Result<()> {
595
+ operation()
596
+ .with_context(|| format!("failed to process file: {}", filename))?;
597
+ Ok(())
598
+ }
599
+ ```
600
+
601
+ ### 错误类型设计
602
+
603
+ ```rust
604
+ // ✅ 使用 #[source] 保留错误链
605
+ #[derive(Debug, thiserror::Error)]
606
+ pub enum ServiceError {
607
+ #[error("database error")]
608
+ Database(#[source] sqlx::Error),
609
+
610
+ #[error("network error: {message}")]
611
+ Network {
612
+ message: String,
613
+ #[source]
614
+ source: reqwest::Error,
615
+ },
616
+
617
+ #[error("validation failed: {0}")]
618
+ Validation(String),
619
+ }
620
+
621
+ // ✅ 为常见转换实现 From
622
+ impl From<sqlx::Error> for ServiceError {
623
+ fn from(err: sqlx::Error) -> Self {
624
+ ServiceError::Database(err)
625
+ }
626
+ }
627
+ ```
628
+
629
+ ---
630
+
631
+ ## 性能
632
+
633
+ ### 避免不必要的 collect()
634
+
635
+ ```rust
636
+ // ❌ 不必要的 collect——中间分配
637
+ fn bad_sum(items: &[i32]) -> i32 {
638
+ items.iter()
639
+ .filter(|x| **x > 0)
640
+ .collect::<Vec<_>>() // 不必要!
641
+ .iter()
642
+ .sum()
643
+ }
644
+
645
+ // ✅ 惰性迭代
646
+ fn good_sum(items: &[i32]) -> i32 {
647
+ items.iter().filter(|x| **x > 0).copied().sum()
648
+ }
649
+ ```
650
+
651
+ ### 字符串拼接
652
+
653
+ ```rust
654
+ // ❌ 字符串拼接在循环中重复分配
655
+ fn bad_concat(items: &[&str]) -> String {
656
+ let mut s = String::new();
657
+ for item in items {
658
+ s = s + item; // 每次都重新分配!
659
+ }
660
+ s
661
+ }
662
+
663
+ // ✅ 预分配或用 join
664
+ fn good_concat(items: &[&str]) -> String {
665
+ items.join("")
666
+ }
667
+
668
+ // ✅ 使用 with_capacity 预分配
669
+ fn good_concat_capacity(items: &[&str]) -> String {
670
+ let total_len: usize = items.iter().map(|s| s.len()).sum();
671
+ let mut result = String::with_capacity(total_len);
672
+ for item in items {
673
+ result.push_str(item);
674
+ }
675
+ result
676
+ }
677
+
678
+ // ✅ 使用 write! 宏
679
+ use std::fmt::Write;
680
+
681
+ fn good_concat_write(items: &[&str]) -> String {
682
+ let mut result = String::new();
683
+ for item in items {
684
+ write!(result, "{}", item).unwrap();
685
+ }
686
+ result
687
+ }
688
+ ```
689
+
690
+ ### 避免不必要的分配
691
+
692
+ ```rust
693
+ // ❌ 不必要的 Vec 分配
694
+ fn bad_check_any(items: &[Item]) -> bool {
695
+ let filtered: Vec<_> = items.iter()
696
+ .filter(|i| i.is_valid())
697
+ .collect();
698
+ !filtered.is_empty()
699
+ }
700
+
701
+ // ✅ 使用迭代器方法
702
+ fn good_check_any(items: &[Item]) -> bool {
703
+ items.iter().any(|i| i.is_valid())
704
+ }
705
+
706
+ // ❌ String::from 用于静态字符串
707
+ fn bad_static() -> String {
708
+ String::from("error message") // 运行时分配
709
+ }
710
+
711
+ // ✅ 返回 &'static str
712
+ fn good_static() -> &'static str {
713
+ "error message" // 无分配
714
+ }
715
+ ```
716
+
717
+ ---
718
+
719
+ ## Trait 设计
720
+
721
+ ### 避免过度抽象
722
+
723
+ ```rust
724
+ // ❌ 过度抽象——不是 Java,不需要 Interface 一切
725
+ trait Processor { fn process(&self); }
726
+ trait Handler { fn handle(&self); }
727
+ trait Manager { fn manage(&self); } // Trait 过多
728
+
729
+ // ✅ 只在需要多态时创建 trait
730
+ // 具体类型通常更简单、更快
731
+ struct DataProcessor {
732
+ config: Config,
733
+ }
734
+
735
+ impl DataProcessor {
736
+ fn process(&self, data: &Data) -> Result<Output> {
737
+ // 直接实现
738
+ }
739
+ }
740
+ ```
741
+
742
+ ### Trait 对象 vs 泛型
743
+
744
+ ```rust
745
+ // ❌ 不必要的 trait 对象(动态分发)
746
+ fn bad_process(handler: &dyn Handler) {
747
+ handler.handle(); // 虚表调用
748
+ }
749
+
750
+ // ✅ 使用泛型(静态分发,可内联)
751
+ fn good_process<H: Handler>(handler: &H) {
752
+ handler.handle(); // 可能被内联
753
+ }
754
+
755
+ // ✅ trait 对象适用场景:异构集合
756
+ fn store_handlers(handlers: Vec<Box<dyn Handler>>) {
757
+ // 需要存储不同类型的 handlers
758
+ }
759
+
760
+ // ✅ 使用 impl Trait 返回类型
761
+ fn create_handler() -> impl Handler {
762
+ ConcreteHandler::new()
763
+ }
764
+ ```
765
+
766
+ ---
767
+
768
+ ## Rust Review Checklist
769
+
770
+ ### 编译器不能捕获的问题
771
+
772
+ **业务逻辑正确性**
773
+ - [ ] 边界条件处理正确
774
+ - [ ] 状态机转换完整
775
+ - [ ] 并发场景下的竞态条件
776
+
777
+ **API 设计**
778
+ - [ ] 公共 API 难以误用
779
+ - [ ] 类型签名清晰表达意图
780
+ - [ ] 错误类型粒度合适
781
+
782
+ ### 所有权与借用
783
+
784
+ - [ ] clone() 是有意为之,文档说明了原因
785
+ - [ ] Arc<Mutex<T>> 真的需要共享状态吗?
786
+ - [ ] RefCell 的使用有正当理由
787
+ - [ ] 生命周期不过度复杂
788
+ - [ ] 考虑使用 Cow 避免不必要的分配
789
+
790
+ ### Unsafe 代码(最重要)
791
+
792
+ - [ ] 每个 unsafe 块有 SAFETY 注释
793
+ - [ ] unsafe fn 有 # Safety 文档节
794
+ - [ ] 解释了为什么是安全的,不只是做什么
795
+ - [ ] 列出了必须维护的不变量
796
+ - [ ] unsafe 边界尽可能小
797
+ - [ ] 考虑过是否有 safe 替代方案
798
+
799
+ ### 异步/并发
800
+
801
+ - [ ] 没有在 async 中阻塞(std::fs、thread::sleep)
802
+ - [ ] 没有跨 .await 持有 std::sync 锁
803
+ - [ ] spawn 的任务满足 'static
804
+ - [ ] 锁的获取顺序一致
805
+ - [ ] Channel 缓冲区大小合理
806
+
807
+ ### 取消安全性
808
+
809
+ - [ ] select! 中的 Future 是取消安全的
810
+ - [ ] 文档化了 async 函数的取消安全性
811
+ - [ ] 取消不会导致数据丢失或不一致状态
812
+ - [ ] 使用 tokio::pin! 正确处理需要重用的 Future
813
+
814
+ ### spawn vs await
815
+
816
+ - [ ] spawn 只用于真正需要并行的场景
817
+ - [ ] 简单操作直接 await,不要 spawn
818
+ - [ ] spawn 的 JoinHandle 结果被正确处理
819
+ - [ ] 考虑任务的生命周期和关闭策略
820
+ - [ ] 优先使用 join!/try_join! 进行结构化并发
821
+
822
+ ### 错误处理
823
+
824
+ - [ ] 库:thiserror 定义结构化错误
825
+ - [ ] 应用:anyhow + context
826
+ - [ ] 没有生产代码 unwrap/expect
827
+ - [ ] 错误消息对调试有帮助
828
+ - [ ] must_use 返回值被处理
829
+ - [ ] 使用 #[source] 保留错误链
830
+
831
+ ### 性能
832
+
833
+ - [ ] 避免不必要的 collect()
834
+ - [ ] 大数据传引用
835
+ - [ ] 字符串用 with_capacity 或 write!
836
+ - [ ] impl Trait vs Box<dyn Trait> 选择合理
837
+ - [ ] 热路径避免分配
838
+ - [ ] 考虑使用 Cow 减少克隆
839
+
840
+ ### 代码质量
841
+
842
+ - [ ] cargo clippy 零警告
843
+ - [ ] cargo fmt 格式化
844
+ - [ ] 文档注释完整
845
+ - [ ] 测试覆盖边界条件
846
+ - [ ] 公共 API 有文档示例