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
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Reviews Rust code for ownership, lifetimes, error handling, safety, and idiomatic patterns.
Cargo.toml in project root.rs filessrc/lib.rs or src/main.rstests/ directory**Rust Edition**: 2021
**Type**: Library / Binary
**Dependencies**: Key crates
**Testing**: cargo test
**Linter**: clippy
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
| 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()
}
| 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
}
| 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");
| 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);
| 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
}
}
}
| 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",
}
}
## 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
cargo clippy -- -W clippy::pedanticrust-api-reviewer: Web framework patternssecurity-scanner: Rust security auditperf-analyzer: Performance profiling