返回 Skill 列表
extension
分类: 开发与工程无需 API Key

rust-reviewer

何时:Rust项目审查、所有权/借用、错误处理、不安全代码、性能 什么:所有权模式 + 生命周期分析 + 错误处理(Result/Option)+ 不安全代码审核 + 符合Rust习惯的写法 何时不适用:Rust API → rust-api-reviewer, Go → go-reviewer

person作者: jakexiaohubgithub

Rust Reviewer Skill

Purpose

Reviews Rust code for ownership, lifetimes, error handling, safety, and idiomatic patterns.

When to Use

  • Rust project code review
  • Ownership/borrowing review
  • Lifetime annotation review
  • Unsafe code audit
  • Error handling patterns

Project Detection

  • Cargo.toml in project root
  • .rs files
  • src/lib.rs or src/main.rs
  • tests/ directory

Workflow

Step 1: Analyze Project

**Rust Edition**: 2021
**Type**: Library / Binary
**Dependencies**: Key crates
**Testing**: cargo test
**Linter**: clippy

Step 2: Select Review Areas

AskUserQuestion:

"Which areas to review?"
Options:
- Full Rust review (recommended)
- Ownership and borrowing
- Lifetimes and references
- Error handling (Result/Option)
- Unsafe code audit
multiSelect: true

Detection Rules

Ownership & Borrowing

| Check | Recommendation | Severity | |-------|----------------|----------| | Unnecessary clone() | Borrow instead | MEDIUM | | &String parameter | Use &str | MEDIUM | | Vec ownership transfer | Consider slice | MEDIUM | | Excessive Rc/Arc | Restructure ownership | MEDIUM |

// BAD: Unnecessary clone
fn process(data: Vec<String>) {
    for item in data.clone().iter() {  // Clone not needed!
        println!("{}", item);
    }
}

// GOOD: Borrow
fn process(data: &[String]) {
    for item in data {
        println!("{}", item);
    }
}

// BAD: &String parameter
fn greet(name: &String) {
    println!("Hello, {}", name);
}

// GOOD: &str parameter (more flexible)
fn greet(name: &str) {
    println!("Hello, {}", name);
}

// BAD: Taking ownership unnecessarily
fn sum(numbers: Vec<i32>) -> i32 {
    numbers.iter().sum()
}

// GOOD: Borrow slice
fn sum(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}

Lifetimes

| Check | Recommendation | Severity | |-------|----------------|----------| | Missing lifetime annotation | Add explicit lifetime | HIGH | | 'static overuse | Use specific lifetime | MEDIUM | | Lifetime in struct | Consider ownership | MEDIUM | | Complex lifetime bounds | Simplify if possible | LOW |

// BAD: Missing lifetime
struct Parser {
    input: &str,  // Error: missing lifetime
}

// GOOD: Explicit lifetime
struct Parser<'a> {
    input: &'a str,
}

impl<'a> Parser<'a> {
    fn new(input: &'a str) -> Self {
        Parser { input }
    }

    fn parse(&self) -> Result<Ast<'a>, Error> {
        // ...
    }
}

// BAD: 'static when not needed
fn process(data: &'static str) {
    // Requires static lifetime!
}

// GOOD: Generic lifetime
fn process(data: &str) {
    // Works with any lifetime
}

// Consider: Ownership instead of lifetime
struct Config {
    name: String,  // Owned, no lifetime needed
}

Error Handling

| Check | Recommendation | Severity | |-------|----------------|----------| | unwrap() in library | Return Result | HIGH | | expect() without message | Add descriptive message | MEDIUM | | panic! for recoverable | Return Err | HIGH | | No custom error type | Define error enum | MEDIUM |

// BAD: unwrap in library code
pub fn parse_config(path: &str) -> Config {
    let content = fs::read_to_string(path).unwrap();  // Will panic!
    serde_json::from_str(&content).unwrap()
}

// GOOD: Return Result
pub fn parse_config(path: &str) -> Result<Config, ConfigError> {
    let content = fs::read_to_string(path)
        .map_err(|e| ConfigError::IoError(e))?;
    serde_json::from_str(&content)
        .map_err(|e| ConfigError::ParseError(e))
}

// GOOD: Custom error type
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
    #[error("failed to read config file: {0}")]
    IoError(#[from] std::io::Error),

    #[error("failed to parse config: {0}")]
    ParseError(#[from] serde_json::Error),

    #[error("missing required field: {0}")]
    MissingField(String),
}

// BAD: expect without message
let value = map.get("key").expect("failed");

// GOOD: Descriptive expect
let value = map.get("key")
    .expect("config must contain 'key' field");

Option Handling

| Check | Recommendation | Severity | |-------|----------------|----------| | if let Some + else | Use map_or/unwrap_or | LOW | | Nested Options | Use and_then/flatten | MEDIUM | | match for single case | Use if let | LOW |

// BAD: Verbose Option handling
let result = match value {
    Some(v) => v.to_string(),
    None => "default".to_string(),
};

// GOOD: Using combinators
let result = value
    .map(|v| v.to_string())
    .unwrap_or_else(|| "default".to_string());

// Or even simpler
let result = value.map_or("default".to_string(), |v| v.to_string());

// BAD: Nested Options
let result: Option<Option<i32>> = Some(Some(42));
let value = match result {
    Some(Some(v)) => v,
    _ => 0,
};

// GOOD: Flatten
let value = result.flatten().unwrap_or(0);

// Using and_then for chaining
let value = get_user(id)
    .and_then(|user| user.profile)
    .and_then(|profile| profile.avatar)
    .map(|avatar| avatar.url);

Unsafe Code

| Check | Recommendation | Severity | |-------|----------------|----------| | Unnecessary unsafe | Remove if safe alternative | HIGH | | No safety comment | Document invariants | HIGH | | Raw pointer dereference | Validate pointer | CRITICAL | | Transmute usage | Use safe cast if possible | CRITICAL |

// BAD: Unsafe without documentation
unsafe fn get_value(ptr: *const i32) -> i32 {
    *ptr
}

// GOOD: Documented unsafe
/// Gets the value at the given pointer.
///
/// # Safety
///
/// - `ptr` must be valid and properly aligned
/// - `ptr` must point to initialized memory
/// - The memory must not be mutated while this reference exists
unsafe fn get_value(ptr: *const i32) -> i32 {
    debug_assert!(!ptr.is_null(), "ptr must not be null");
    *ptr
}

// BAD: Transmute for casting
let bytes: [u8; 4] = unsafe { std::mem::transmute(value) };

// GOOD: Safe alternative
let bytes = value.to_ne_bytes();

// Encapsulating unsafe in safe API
pub struct Buffer {
    ptr: *mut u8,
    len: usize,
}

impl Buffer {
    /// Creates a new buffer. Safe because we control allocation.
    pub fn new(size: usize) -> Self {
        let ptr = unsafe { alloc(Layout::array::<u8>(size).unwrap()) };
        Self { ptr, len: size }
    }

    /// Safe accessor - bounds checked
    pub fn get(&self, index: usize) -> Option<u8> {
        if index < self.len {
            // SAFETY: index is bounds-checked above
            Some(unsafe { *self.ptr.add(index) })
        } else {
            None
        }
    }
}

Idiomatic Rust

| Check | Recommendation | Severity | |-------|----------------|----------| | C-style loop | Use iterators | LOW | | Manual index tracking | Use enumerate() | LOW | | Return in match | Return match expression | LOW | | Redundant type annotation | Use inference | LOW |

// BAD: C-style loop
let mut i = 0;
while i < items.len() {
    process(&items[i]);
    i += 1;
}

// GOOD: Iterator
for item in &items {
    process(item);
}

// With index
for (i, item) in items.iter().enumerate() {
    println!("{}: {}", i, item);
}

// BAD: Redundant type
let numbers: Vec<i32> = vec![1, 2, 3];
let sum: i32 = numbers.iter().sum();

// GOOD: Type inference
let numbers = vec![1, 2, 3];
let sum: i32 = numbers.iter().sum();  // Only needed here

// BAD: Return in match arms
fn classify(n: i32) -> &'static str {
    match n {
        0 => { return "zero"; }
        _ if n > 0 => { return "positive"; }
        _ => { return "negative"; }
    }
}

// GOOD: Match as expression
fn classify(n: i32) -> &'static str {
    match n {
        0 => "zero",
        _ if n > 0 => "positive",
        _ => "negative",
    }
}

Response Template

## Rust Code Review Results

**Project**: [name]
**Rust Edition**: 2021 | **Clippy**: Enabled

### Ownership & Borrowing
| Status | File | Issue |
|--------|------|-------|
| MEDIUM | parser.rs:45 | Unnecessary clone() |

### Lifetimes
| Status | File | Issue |
|--------|------|-------|
| HIGH | cache.rs:23 | Missing lifetime on struct field |

### Error Handling
| Status | File | Issue |
|--------|------|-------|
| HIGH | lib.rs:67 | unwrap() in public function |

### Unsafe
| Status | File | Issue |
|--------|------|-------|
| CRITICAL | ffi.rs:12 | Unsafe block without safety comment |

### Recommended Actions
1. [ ] Replace clone() with borrowing
2. [ ] Add lifetime annotations to Cache struct
3. [ ] Return Result instead of panicking
4. [ ] Document all unsafe blocks with safety invariants

Best Practices

  1. Ownership: Borrow when possible, clone when needed
  2. Lifetimes: Explicit > implicit, owned > referenced
  3. Errors: thiserror for libs, anyhow for apps
  4. Unsafe: Minimize, document, encapsulate in safe API
  5. Clippy: Run with cargo clippy -- -W clippy::pedantic

Integration

  • rust-api-reviewer: Web framework patterns
  • security-scanner: Rust security audit
  • perf-analyzer: Performance profiling