Files
gh-physics91-claude-vibe/skills/rust-reviewer/SKILL.md
2025-11-30 08:47:23 +08:00

9.0 KiB

name, description
name description
rust-reviewer WHEN: Rust project review, ownership/borrowing, error handling, unsafe code, performance WHAT: Ownership patterns + Lifetime analysis + Error handling (Result/Option) + Unsafe audit + Idiomatic Rust WHEN NOT: Rust API → rust-api-reviewer, Go → go-reviewer

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