home / skills / physics91 / claude-vibe / rust-reviewer
This skill performs thorough Rust code reviews focusing on ownership, lifetimes, error handling, unsafe auditing, and idiomatic patterns to improve safety and
npx playbooks add skill physics91/claude-vibe --skill rust-reviewerReview the files below or copy the command above to add this skill to your agents.
---
name: rust-reviewer
description: |
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 |
```rust
// 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 |
```rust
// 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 |
```rust
// 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 |
```rust
// 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 |
```rust
// 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 |
```rust
// 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
This skill reviews Rust projects for ownership and borrowing correctness, lifetime annotations, error-handling patterns, unsafe code usage, and overall idiomatic style. It highlights risky patterns, suggests concrete fixes, and provides prioritized recommendations to improve safety and maintainability. Use it to catch subtle lifetime bugs, remove unnecessary clones, and harden unsafe blocks with documented invariants.
The reviewer scans Cargo.toml and .rs source files to detect project type, edition, and key crates, then analyzes ownership flows, borrow scopes, and lifetime annotations across functions and structs. It flags unwrap/expect/panic usages in public APIs, evaluates Option/Result handling idioms, and audits unsafe blocks for missing safety comments or raw pointer misuse. The tool also recommends idiomatic replacements (iterators, &str vs &String, slicing) and produces a prioritized list of findings and remediation steps.
Can this skill modify code automatically?
No. It provides actionable findings and suggested diffs, but leaves code changes to the developer or CI automation you control.
Does it run Clippy and tests?
It reports Clippy recommendations and suggests running cargo test and cargo clippy; integrating automatic runs is handled externally by your CI.