rust-pr-review
Skill2 starsChanged 4 months agoMITView on GitHub
Review a Rust PR, branch, or local diff against main. Focus on correctness, smaller code, idiomatic Rust, ownership, error handling, unnecessary clones, needless allocation, async safety, tests, and maintainability.
In this repository25 other files it gives its agents.
- brutal-deepresearchSkill
- brutal-grillSkill
- brutal-idea-evalSkill
- brutal-inf-fix-loopSkill
- brutal-planSkill
- brutal-pr-finding-fixerSkill
- brutal-pr-fix-loopSkill
- brutal-project-reviewSkill
- brutal-pr-reviewSkill
- brutal-realitySkill
- brutal-reviewSkill
- brutal-specSkill
- brutal-swarmSkill
- brutal-ticketsSkill
- brutal-wayfinderSkill
- brutal-workerSkill
- codex-coopSkill
- design-jepa-modelsSkill
- factory-designSkill
- funcvec-dedupe-fixerSkill
- grill-meSkill
- handoffSkill
- opencode-delegateSkill
- rust-architecture-reviewSkill
- supply-chain-analyzerSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
59 rules. About 0 are common, 42 are distinctive, and 17 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈42
Distinctive · estimate
This author's own choices. Worth a look.
17
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 16
Use this skill when asked to review a Rust pull request, branch, patch, or local diff.
- Distinctive: line 20
Prefer concrete fixes. Avoid style churn.
- Distinctive: line 26
Prefer feedback that makes the code:
- Very specific: line 36
Do not nitpick formatting.
- Distinctive: line 38
Do not optimize for fewer lines by itself.
- Distinctive: line 44
Do not rewrite unrelated files.
- Distinctive: line 45
Do not suggest broad architecture changes unless the diff introduced the problem.
- Distinctive: line 46
Do not ask for refactors outside the touched area unless they are required for correctness or safety.
- Distinctive: line 47
Do not change tests to make a refactor pass.
- Distinctive: line 48
Do not remove error handling, logging, metrics, or user-facing behavior just to make code shorter.
- Distinctive: line 50
When in doubt, prefer a small targeted comment over a broad rewrite.
- Very specific: line 64
If main...HEAD is empty, check staged and unstaged changes:
- Distinctive: line 73
If main is missing, inspect remotes and choose the likely base:
- Distinctive: line 81
If the repository uses master instead of main, compare against master.
- Distinctive: line 95
Before making findings, inspect the Rust project shape:
- Very specific: line 101
Read the relevant Cargo.toml files and note:
- Distinctive: line 111
Read the changed files directly, not just the diff.
- Very specific: line 126
Prefer the project's own commands first.
- Distinctive: line 143
If all features are impossible because of mutually exclusive features, use the feature set relevant to the changed crate.
- Distinctive: line 154
If checks fail, distinguish existing failures from failures caused by the diff.
- Distinctive: line 156
Do not bury compiler or Clippy findings under speculative review comments.
- Very specific: line 184
If input can come from a file, CLI, network, environment variable, or user input, this should return an error with context.
- Very specific: line 190
If user_supplied can contain absolute paths or .., this may escape base.
- Distinctive: line 264
delete dead branches and unused helpers
- Very specific: line 267
remove intermediate Vecs when an iterator can flow directly
- Very specific: line 268
use entry() for map insert/update logic
- Very specific: line 269
use then_some / then for simple conditional values
- Very specific: line 270
use matches! for simple enum checks
- Very specific: line 271
use let Some(x) = ... else { ...
- Very specific: line 272
use strip_prefix, strip_suffix, split_once, or rsplit_once instead of manual indexing
- Very specific: line 273
use retain instead of rebuilding a collection when mutating in place is clearer
- Very specific: line 274
use mem::take or Option::take when moving out of a field intentionally
- Very specific: line 355
use of std::sync::Mutex in async hot paths
- Distinctive: line 386
Only raise performance issues that are visible from the code or relevant to the changed path.
- Distinctive: line 412
Do not request micro-optimizations without a concrete reason.
- Distinctive: line 418
read the full unsafe block and safe API around it
- Distinctive: line 420
check aliasing, lifetimes, initialization, alignment, provenance, and panic safety
- Distinctive: line 421
verify the safe wrapper prevents misuse
- Distinctive: line 454
Check whether changed behavior has tests.
- Distinctive: line 469
Prefer behavior-focused test names:
- Distinctive: line 482
use real network services unless explicitly integration tests
- Distinctive: line 483
use broad snapshots that will churn constantly
- Distinctive: line 484
only test the new happy path when error behavior changed
- Distinctive: line 492
Only for issues that can break correctness, safety, security, data integrity, public API compatibility, or production behavior.
- Distinctive: line 501
test or build failure caused by the PR
- Distinctive: line 507
Should fix before merge unless there is a good reason.
- Distinctive: line 533
Do not inflate severity. Many Rust idiom comments are nice-to-have unless they affect API design, correctness, or likely performance.
- Distinctive: line 537
Start with the result:
- Very specific: line 543
Use changes requested only when there is at least one blocking issue or an important issue that should clearly block merge.
- Very specific: line 586
If there are no findings in a section, write None.
- Distinctive: line 628
Prefer patches over vague advice.
- Distinctive: line 630
Do not report uncertain issues.
- Distinctive: line 634
When suggesting code, keep the patch minimal.
- Distinctive: line 644
Do not rewrite the whole module unless the bug requires it.
- Distinctive: line 646
When changing signatures, mention likely call-site impact.
- Distinctive: line 648
When suggesting a more idiomatic construct, include the exact replacement and why it preserves behavior.
- Distinctive: line 652
Use this checklist during review:
- Distinctive: line 675
run large unrelated refactors
- Distinctive: line 680
remove explicit code just because it is longer
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Rust PR Review
Use this skill when asked to review a Rust pull request, branch, patch, or local diff.
The job is to review changed code against main, then report only findings that are worth the author's time.
Prefer concrete fixes. Avoid style churn.
What this skill optimizes for
Review the diff as a Rust reviewer.
Prefer feedback that makes the code:
- correct
- smaller without becoming clever
- more idiomatic Rust
- easier to reason about
- safer around ownership, lifetimes, errors, and async boundaries
- cheaper in allocations, clones, locks, and repeated work
- better covered by tests
Do not nitpick formatting. Let rustfmt and Clippy handle mechanical style.
Do not optimize for fewer lines by itself. Optimize for code that preserves behavior, reduces complexity, and is harder to misuse.
Scope
Review only changed code and the surrounding context needed to understand it.
Do not rewrite unrelated files. Do not suggest broad architecture changes unless the diff introduced the problem. Do not ask for refactors outside the touched area unless they are required for correctness or safety. Do not change tests to make a refactor pass. Do not remove error handling, logging, metrics, or user-facing behavior just to make code shorter.
When in doubt, prefer a small targeted comment over a broad rewrite.
Step 1: Identify the review target
For a local branch:
git status --short
git branch --show-current
git diff --stat main...HEAD
git diff main...HEAD
git log --oneline main..HEADIf main...HEAD is empty, check staged and unstaged changes:
git diff --staged --stat
git diff --staged
git diff --stat
git diffIf main is missing, inspect remotes and choose the likely base:
git remote -v
git branch -a
git merge-base origin/main HEAD 2>/dev/null || git merge-base origin/master HEADIf the repository uses master instead of main, compare against master.
For a GitHub PR, prefer a separate worktree or read-only checkout. Do not switch the user's current branch unless explicitly asked.
Useful PR commands when gh is available:
gh pr view --json title,body,baseRefName,headRefName,author,commits,files
gh pr diff
gh pr checksStep 2: Get Rust context
Before making findings, inspect the Rust project shape:
find . -name Cargo.toml -maxdepth 4Read the relevant Cargo.toml files and note:
- Rust edition
rust-version/ MSRV, if present- workspace layout
- crate type: library, binary, proc macro, test helper, example
- relevant features
- lint settings
- major dependencies:
tokio,serde,thiserror,anyhow,tracing,clap, database clients, HTTP clients
Read the changed files directly, not just the diff.
For any serious finding, read the whole function, trait impl, module, or unsafe block around the changed line.
Also inspect nearby code for:
- existing helper functions
- existing error types
- established naming patterns
- existing test style
- public API call sites
- feature-gated code paths
Step 3: Run deterministic checks when practical
Prefer the project's own commands first. Check README, CI config, justfile, Makefile, .github/workflows, xtask, or repo docs for canonical commands.
Common Rust checks:
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-featuresFor workspaces, narrow the package if the full workspace is too large:
cargo clippy -p <package> --all-targets --all-features -- -D warnings
cargo test -p <package> --all-featuresIf all features are impossible because of mutually exclusive features, use the feature set relevant to the changed crate.
Other useful checks:
cargo check --all-targets
cargo test -p <package> <test_name>
cargo test --doc
cargo bench --no-runIf checks fail, distinguish existing failures from failures caused by the diff.
Do not bury compiler or Clippy findings under speculative review comments. Deterministic tool output comes first.
Step 4: Review priorities
1. Correctness
Look for:
- behavior changes not covered by tests
- panics on user input, config, filesystem, network, or parsed data
- silent error swallowing
- changed ordering, filtering, retries, deduping, or default behavior
- overflow, truncation, sign conversion, and lossy casts
- path handling problems
- UTF-8 assumptions
- partial writes, partial reads, cancellation, cleanup, and rollback bugs
- serialization/deserialization compatibility changes
- public API behavior changes without migration notes or tests
- feature flag behavior changes
- concurrency behavior that differs between single-threaded and multi-threaded runtimes
- nondeterminism introduced into tests or production behavior
Examples worth flagging:
let value = input.parse::<u64>().unwrap();If input can come from a file, CLI, network, environment variable, or user input, this should return an error with context.
let path = base.join(user_supplied);If user_supplied can contain absolute paths or .., this may escape base.
2. Idiomatic ownership and borrowing
Prefer:
&strover&String&[T]over&Vec<T>&Pathover&PathBuf- borrowed parameters when the function only reads
- owned parameters when the function stores, consumes, or transfers ownership
- moving values instead of cloning when ownership is already available
Cow<'a, str>orCow<'a, [T]>when ownership is conditionalArc<T>only when shared ownership is realRc<T>for single-threaded shared ownership, if already used and appropriate- lifetime elision when explicit lifetimes add no useful information
Flag:
.clone()used only to appease the borrow checker.to_string()/.to_owned()on hot or repeated paths- cloning inside loops
- collecting into
Vecjust to iterate once - taking
String,PathBuf, orVec<T>when a borrow would work - broad
'staticbounds used to dodge lifetime design - unnecessary lifetime parameters
- APIs that force callers to allocate
- converting borrowed data to owned data too early
Good comment:
This takes `String`, but the function only reads it. Taking `&str` avoids forcing callers to allocate and still lets existing `String` callers pass `name.as_str()`.3. Error handling
Prefer:
Result<T, E>for expected failures?for propagationthiserrorfor library error typesanyhow/eyreat app boundaries, if already used.with_context(...)or equivalent at I/O, parse, network, and config boundariesok_or_else/map_errwhen errors need construction or context- typed errors when callers need to branch on failure kind
Flag:
- production
unwrap - production
expecton recoverable failure - panic paths in library code
- string errors where callers need structure
- errors logged and then discarded
map_err(|_| ...)that destroys useful diagnostics- returning
Optionwhen the caller needs to know why something failed - using
anyhow::Errordeep inside reusable library APIs when a typed error would be better
Acceptable uses of unwrap / expect:
- tests
- examples where failure would obscure the example
- constants or invariants that cannot fail, with a clear explanation
- code where the invariant is already checked immediately before
Even then, prefer comments that explain the invariant.
4. Smaller and clearer code
Suggest simplifications when they preserve behavior.
Good simplifications:
- early returns instead of deeply nested branches
- delete dead branches and unused helpers
- merge duplicate match arms
- replace manual
Option/Resultplumbing with?,ok_or_else,map,map_err,transpose,flatten, oris_some_and - remove intermediate
Vecs when an iterator can flow directly - use
entry()for map insert/update logic - use
then_some/thenfor simple conditional values - use
matches!for simple enum checks - use
let Some(x) = ... else { ... };when it makes the happy path clearer - use
strip_prefix,strip_suffix,split_once, orrsplit_onceinstead of manual indexing - use
retaininstead of rebuilding a collection when mutating in place is clearer - use
mem::takeorOption::takewhen moving out of a field intentionally
Bad simplifications:
- dense iterator chains that hide stateful logic
- clever one-liners
- removing error context
- changing user-facing strings, logs, metrics, or serialized output unless asked
- changing tests to fit a refactor
- refactoring unrelated code
- replacing a clear loop with an iterator chain that needs side effects and mutation anyway
Example useful simplification:
let Some(config) = maybe_config else {
return Ok(None);
};
let value = parse_config(config)?;
Ok(Some(value))This is often clearer than nested match when the happy path is the main path.
5. Traits and APIs
Prefer:
- small, cohesive traits
Fromfor infallible conversionsTryFromfor fallible conversions- newtypes for domain identifiers and validated values
#[must_use]on builders and meaningful return valuespub(crate)beforepub- sealed traits when external impls would be a breaking-compatibility trap
impl Traitor generics when dynamic dispatch is not neededBox<dyn Trait>only when runtime heterogeneity or object safety is actually needed- explicit ownership semantics in names and signatures
Flag:
- primitive obsession in public APIs
- public fields that should be invariant-protected
- blanket trait bounds wider than needed
- needless
Send + Sync + 'static Derefused as inheritance- APIs that force allocation on callers
- public APIs that expose implementation details
- trait methods that make the trait impossible to object-safe without need
- unnecessary generic parameters that leak complexity into callers
Examples:
fn read_config(path: &Path) -> Result<Config>is usually better than:
fn read_config(path: PathBuf) -> Result<Config>when the function only reads the path.
pub(crate) struct Parseris better than pub struct Parser unless downstream crates need it.
6. Async and concurrency
Look for:
- holding
MutexGuard,RwLockGuard, or borrowed state across.await - blocking I/O in async functions
- unbounded channels without a reason
- spawned tasks with ignored
JoinHandles - cancellation paths that leak work or lose errors
- use of
std::sync::Mutexin async hot paths - lock ordering problems
- retries without backoff or cancellation
select!branches that drop important futures unexpectedly- async functions that never yield in long loops
- accidentally serial work where concurrency was intended
- accidentally concurrent work where ordering matters
Prefer:
- small lock scopes
- clone/move before
.awaitwhen it releases a lock tokio::task::spawn_blockingfor blocking worktry_join!when failures should short-circuitJoinSetwhen managing many tasks- bounded channels unless unbounded growth is intentional
- cancellation-safe APIs where needed
- explicit timeouts for network calls when the project uses timeouts elsewhere
Example issue:
let mut state = self.state.lock().await;
let response = self.client.fetch().await?;
state.last_response = Some(response);The lock is held during network I/O. Prefer fetching outside the lock, then locking only to update state.
7. Performance and allocation
Only raise performance issues that are visible from the code or relevant to the changed path.
Look for:
- repeated allocation in loops
- needless
format! collect::<Vec<_>>()only to iterate- repeated regex/parser construction
- O(n²) scans introduced in changed code
- avoidable string copies
- large enum variants
- missing
with_capacitywhen size is known - avoidable locks on hot paths
- cloning large structs
- repeated filesystem or network calls inside loops
- using
containson aVecin repeated membership checks where aHashSetwould be clearer and cheaper
Prefer:
with_capacitywhen size is knownentry()for map insert/update- lazy iterators without intermediate collections
write!into an existing buffer instead of repeatedformat!in hot paths- references and slices for zero-copy paths
- caching compiled regexes or parsers when construction is expensive
Do not request micro-optimizations without a concrete reason.
8. Unsafe
For any changed unsafe:
- read the full unsafe block and safe API around it
- identify the safety invariant
- check aliasing, lifetimes, initialization, alignment, provenance, and panic safety
- verify the safe wrapper prevents misuse
- require a
// SAFETY:comment that explains why the block is valid
Unsafe issues are blocking when the invariant is wrong or unenforced.
Look for:
- invalid pointer casts
- unchecked indexing without a proven bound
- assuming layout without
repr(...) - incorrect
Send/Syncimpls - creating references to uninitialized or aliased memory
- panic paths that leave data structures invalid
- safe APIs that allow callers to violate the unsafe invariant
9. Serialization and compatibility
For changes involving serde, wire formats, config, CLI args, database rows, or public structs, check:
- renamed fields
- default behavior
- skipped fields
- enum representation
- backward compatibility
- migration behavior
- unknown-field handling
- optional vs required fields
- stable ordering when output is compared or cached
Flag changes that break existing users without tests or migration notes.
10. Tests
Check whether changed behavior has tests.
Look for:
- happy path
- error path
- empty input
- boundary values
- malformed input
- async timeout/cancellation
- serialization compatibility
- public API examples or doctests
- feature-specific behavior
- regression tests for fixed bugs
Prefer behavior-focused test names:
returns_error_when_config_file_is_missing
does_not_retry_after_permanent_failure
preserves_existing_order_when_dedupingFlag tests that:
- assert implementation details instead of behavior
- rely on wall-clock timing without control
- depend on test order
- use real network services unless explicitly integration tests
- use broad snapshots that will churn constantly
- only test the new happy path when error behavior changed
Severity
Use these buckets.
Blocking
Only for issues that can break correctness, safety, security, data integrity, public API compatibility, or production behavior.
Examples:
- panic on external input
- unsound unsafe
- data loss
- race condition
- swallowed critical error
- test or build failure caused by the PR
- public API break in a library crate without versioning or migration plan
- security-sensitive path, auth, permission, or parsing bug
Important
Should fix before merge unless there is a good reason.
Examples:
- unnecessary clone in a hot path
- missing error context at an important boundary
- public API forces allocation
- fragile async cancellation
- missing tests for changed behavior
- confusing ownership API
- duplicated code that already caused inconsistent behavior
- avoidable O(n²) behavior on expected input sizes
Nice-to-have
Small idiom, cleanup, or maintainability improvements.
Examples:
&Stringcould be&str- duplicated branches
- minor iterator cleanup
entry()would simplify map update logic- a small helper would remove repeated parsing code
- a test name could be more behavior-focused
Do not inflate severity. Many Rust idiom comments are nice-to-have unless they affect API design, correctness, or likely performance.
Output format
Start with the result:
Verdict: approve | approve with nits | changes requestedUse changes requested only when there is at least one blocking issue or an important issue that should clearly block merge.
Then use this structure:
### Blocking
- `path/to/file.rs:123` — Title
What is wrong.
Why it matters.
Suggested change:
```rust
// replacement
```
### Important
- `path/to/file.rs:456` — Title
What is wrong.
Why it matters.
Suggested change:
```rust
// replacement
```
### Nice-to-have
- `path/to/file.rs:789` — Title
Small cleanup or idiom suggestion.
### Things I would not change
Mention tempting rewrites that are not worth doing.
### Checks
- `cargo fmt --check`: pass/fail/not run
- `cargo clippy --all-targets --all-features -- -D warnings`: pass/fail/not run
- `cargo test --all-features`: pass/fail/not runIf there are no findings in a section, write None.
Comment style
Be specific.
Bad:
This is not idiomatic Rust.Good:
This takes `String`, but the function only reads it. Taking `&str` avoids forcing callers to allocate.Bad:
Can this be simpler?Good:
Both branches parse the same value and only differ in the fallback. Pulling the parse before the branch removes the duplicate error path.Bad:
Use iterators.Good:
This `collect::<Vec<_>>()` is only used by the next `for` loop. Keeping the iterator lazy avoids an allocation and removes the temporary vector.Prefer patches over vague advice.
Do not report uncertain issues. If it might be a problem but you cannot prove it from the diff and surrounding code, put it under “Nice-to-have” or skip it.
Suggested patch style
When suggesting code, keep the patch minimal.
Good:
fn normalize_name(name: &str) -> String {
name.trim().to_ascii_lowercase()
}Do not rewrite the whole module unless the bug requires it.
When changing signatures, mention likely call-site impact.
When suggesting a more idiomatic construct, include the exact replacement and why it preserves behavior.
Rust idiom checklist
Use this checklist during review:
- Can owned parameters become borrowed parameters?
- Can
&String,&Vec<T>, or&PathBufbecome&str,&[T], or&Path? - Are there unnecessary
.clone(),.to_string(),.to_owned(), orformat!calls? - Is a
Veccollected only to be iterated once? - Can nested
match/if letbecome?,let else,transpose,flatten, or early returns? - Is
unwraporexpectreachable from production input? - Are errors preserving enough context?
- Are trait bounds narrower than necessary?
- Is visibility narrower than necessary?
- Is dynamic dispatch actually needed?
- Are locks held across
.await? - Are blocking calls inside async code?
- Are spawned tasks observed, cancelled, or joined?
- Are unsafe invariants documented and enforced by the safe API?
- Are tests covering error behavior, not just the happy path?
- Did the PR change serialization, CLI, config, or public API compatibility?
Non-goals
Do not:
- run large unrelated refactors
- debate formatting choices handled by
rustfmt - suggest nightly-only APIs unless the project already uses nightly
- suggest dependencies for tiny helpers
- change MSRV assumptions without checking
rust-version - remove explicit code just because it is longer
- replace simple loops with complex iterator chains
- make user-facing behavior change as part of cleanup
- turn every
StringintoCowby default - turn every trait object into generics by default
- turn every clone into lifetime complexity
The best review is small, accurate, and easy for the author to apply.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

