9.0 KiB
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.tomlin project root.rsfilessrc/lib.rsorsrc/main.rstests/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
- Ownership: Borrow when possible, clone when needed
- Lifetimes: Explicit > implicit, owned > referenced
- Errors: thiserror for libs, anyhow for apps
- Unsafe: Minimize, document, encapsulate in safe API
- Clippy: Run with
cargo clippy -- -W clippy::pedantic
Integration
rust-api-reviewer: Web framework patternssecurity-scanner: Rust security auditperf-analyzer: Performance profiling