Install
openclaw skills install rust-code-reviewReviews Rust code for ownership, borrowing, lifetime, error handling, trait design, unsafe usage, and common mistakes. Use when reviewing .rs files, checking borrow checker issues, error handling patterns, or trait implementations. Covers Rust 2024 edition patterns and modern idioms.
openclaw skills install rust-code-reviewFollow this sequence to avoid false positives and catch edition-specific issues:
Cargo.toml — Note the Rust edition (2018, 2021, 2024) and MSRV if set. Edition 2024 introduces breaking changes to unsafe semantics, RPIT lifetime capture, temporary scoping, and ! type fallback. This determines which patterns apply. Check workspace structure if present.These steps are sequenced: do not skip ahead with “mental verification.” Each step has an objective Pass you can satisfy from files on disk and your own read path.
Cargo.toml (package or workspace manifest) and can state edition and rust-version (if set) in one line.unsafe block, or impl / trait item that contains the cited line (not only a diff hunk).beagle-rust:review-verification-protocol is loaded and every step in it that applies to this review is completed (do not substitute a vague “I checked”).Report findings as:
[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.
| Issue Type | Reference |
|---|---|
| Ownership transfers, borrowing, lifetimes, clone traps, iterators | references/ownership-borrowing.md |
| Lifetime variance, covariance/invariance, memory regions | references/lifetime-variance.md |
Result/Option handling, thiserror, anyhow, opaque vs enumerated errors, deferred-cleanup with ? | references/error-handling.md |
| Async pitfalls, Send/Sync bounds, poll contract, Pin mechanics, cancellation soundness | references/async-concurrency.md |
| Send/Sync semantics, atomics, memory ordering, lock patterns | references/concurrency-primitives.md |
| Memory ordering decision tree, fences, ABA, out-of-thin-air | references/memory-ordering.md |
| Hand-rolled spinlocks, channels, Arc, seqlock, CAS retry patterns | references/lock-free-patterns.md |
| Shared-memory vs worker-pool vs actor design, async vs threads, race-condition vs data race | references/concurrency-models.md |
| Type layout, alignment, repr, PhantomData, generics vs dyn Trait, wide pointers, auto-trait leakage | references/types-layout.md |
| Object safety, ergonomic trait impls, Deref discipline, fallible destructors, hidden contracts, is_normal | references/interface-design.md |
| Index pointers, drop guards, extension traits, crate preludes | references/patterns-in-the-wild.md |
| Unsafe code, API design, derive patterns, clippy patterns | references/common-mistakes.md |
| Validity vs safety, drop check, may_dangle, provenance, panic safety in unsafe, MaybeUninit, Miri | references/unsafe-deep.md |
For development guidance on performance, pointer types, type state, clippy config, iterators, generics, and documentation, use the
beagle-rust:rust-best-practicesskill.
.clone() to silence the borrow checker (hiding design issues).clone() inside loops — prefer .cloned() or .copied() on iterators'static when shorter lifetime works)-> impl Trait) captures all in-scope lifetimes by default; use + use<'a> for precise capture control&str preferred over String, &[T] over Vec<T> in function parametersimpl AsRef<T> or Into<T> used for flexible API parametersCell, RefCell, Mutex) used only when shared mutation is genuinely neededCopy and are passed by valueCow<'_, T> used when ownership is ambiguous.collect() — pass iterators directly when the consumer accepts them.sum() preferred over .fold() for summation (compiler optimizes better)_or_else variants used when fallbacks involve allocationif let temporaries drop at end of the if let — code relying on temporaries living through the else branch needs restructuringBox<[T]> implements IntoIterator — prefer direct iteration over into_vec() firstResult<T, E> used for recoverable errors, not panic!/unwrap/expect#[error("...")] or manual Display)? operator used with proper From implementations or .map_err()unwrap() / expect() only in tests, examples, or provably-safe contextsanyhow used in applications, thiserror in libraries (or clear rationale for alternatives)_or_else variants used when fallbacks involve allocation (ok_or_else, unwrap_or_else)let-else used for early returns on failure (let Ok(x) = expr else { return ... })inspect_err used for error logging, map_err for error transformationderive macros appropriate for the type (Clone, Debug, PartialEq used correctly)struct UserId(Uuid) not bare Uuid)From/Into implementations are lossless and infallible; TryFrom for fallible conversionsSend + Sync bounds verified for types shared across threads#[diagnostic::on_unimplemented] used on public traits to provide clear error messages when users forget to implement themDetailed guidance: references/interface-design.md
dyn-intended traits don't use Self by value, generic params, or associated constants (or are gated where Self: Sized)&T, &mut T, Box<T> so reference and smart-pointer arguments workIntoIterator for &Self and &mut Self, not just SelfDeref only used for transparent forwarding, never as "inheritance" — inherent-method ambiguity is a real bug classclose()/shutdown() returning Result; Drop is best-effort fallback onlyblock_on(...) or new runtime in Drop (deadlock under async runtimes)fn is_normal<T: Sized + Send + Sync + Unpin>() {} test so auto-trait regressions surface at build timefn name(&self) not fn get_name(&self) (reserve get_* for Option-returning or interesting lookups)as_* is cheap reference-to-reference, to_* may allocate, into_* consumes — verify the cost matches the prefixDebug, Clone, Default, PartialEq, Eq, Hash) considered for every public type; Copy only when truly cheap and value-like (removing it later is breaking)Detailed guidance: references/patterns-in-the-wild.md
slotmap::DefaultKey, petgraph::NodeIndex) — bare usize orphans after Vec::swap_removelet _guard = ..., never let _ = ... (the second drops immediately, not at scope end)panic = "abort" (destructors do not run)impl directlyprelude module additions treated as semver-minor (RFC 1105); reserve for major releases when possibleDetailed guidance: references/concurrency-models.md
.await pointsasync is not conflated with parallelism — join! interleaves on one thread; only tokio::spawn (or equivalent on a multi-threaded runtime) parallelizesfetch_add/sharding (CAS is O(N²) under contention)println! / dbg! not used as a debugging tool for race conditions (the Stdout mutex changes the race)unsafe blocks have safety comments explaining invariantsunsafe is minimal — only the truly unsafe operation is inside the blockunsafe trait implementations justify why the contract is upheldunsafe fn bodies use explicit unsafe {} blocks around unsafe ops (unsafe_op_in_unsafe_fn is deny)extern "C" {} blocks written as unsafe extern "C" {}#[no_mangle] and #[export_name] written as #[unsafe(no_mangle)] and #[unsafe(export_name)]Detailed guidance: references/memory-ordering.md, references/lock-free-patterns.md
Send / Sync bounds; unsafe impl Send/Sync carries a comment naming the invariantRelease/Acquire on the same atomic, or a fence); Release publishes data, Acquire observes itSeqCst by default — only when two or more independent atomics need a single global total order, with a comment naming the requirementstore(.., Acquire) / load(.., Release) / load(.., AcqRel) (rejected by the type half they occupy)Relaxed not used to publish or observe non-atomic data (use Release / Acquire)compare_exchange_weak used inside retry loops; strong compare_exchange reserved for one-shot updates; success ordering at least Acquire when acquiring a critical sectionstd::hint::spin_loop() in the busy wait, exponential backoff, and an eventual thread::yield_now(); not used in normal user-space binaries without a documented reason a Mutex is unsuitableArc clones with Relaxed, drops with Release + fence(Acquire) on the last decrement, and includes an overflow guardArc<Mutex<...>> cycles broken with Weak; Arc<Mutex<Copy>> reviewed for Arc<AtomicT> replacement#[cfg(loom)] test module and a Miri-runnable test (no blanket cfg_attr(miri, ignore))OnceLock / LazyLock preferred over once_cell / lazy_static for new code (MSRV ≥ 1.80)MutexGuard held across .await (use tokio::sync::Mutex or drop the guard first)CachePadded or #[repr(align(64))] to avoid false sharingUnsafeCell<T> (not bare *mut T or transmuted & to &mut)AtomicPtr<Node>) uses epoch / hazard-pointer / tagged-pointer reclamation; ABA hazards consideredPascalCase, functions/methods snake_case, constants SCREAMING_SNAKE_CASEsnake_caseis_, has_, can_ prefixes for boolean-returning methodsself (not &mut self) for chaining///)#[must_use] on functions where ignoring the return value is likely a bug#[expect(clippy::...)] preferred over #[allow(...)] for lint suppressionDetailed guidance:
beagle-rust:rust-best-practicesskill (references/performance.md)
&str over String, &[T] over Vec<T>)collect() type is specified or inferableVec::with_capacity() used when size is known.to_string() / .to_owned() chains.collect() when passing iterators directly works.sum() preferred over .fold() for summationimpl Trait) used over dynamic (dyn Trait) unless flexibility requiredDetailed guidance:
beagle-rust:rust-best-practicesskill (references/clippy-config.md)
Cargo.toml ([workspace.lints.clippy] or [lints.clippy])#[expect(clippy::lint)] used over #[allow(...)] — warns when suppression becomes staleredundant_clone, large_enum_variant, needless_collect, perf groupcargo clippy --all-targets --all-features -- -D warnings passesmissing_docs, broken_intra_doc_links)Detailed guidance:
beagle-rust:rust-best-practicesskill (references/type-state-pattern.md)
PhantomData<State> used for zero-cost compile-time state machines (not runtime enums/booleans)self and return new state type (prevents reuse of old state)unsafe code with unsound invariants or undefined behaviorunwrap() on user input or external data in production codeArc<Mutex<...>> without weak referencesreturn err equivalent).clone() masking ownership design issues in hot pathsSend/Sync bounds on types used across threadspanic! for recoverable errors in library code'static lifetimes hiding API design issuesString parameter where &str or impl AsRef<str> would workCargo.toml#[must_use] or #[non_exhaustive]Error trait impls → error-handling.mdSized/?Sized → types-layout.mdDeref discipline, fallible/blocking destructors, hidden contracts, naming → interface-design.mdbeagle-rust:rust-best-practices skillThese are acceptable Rust patterns — reporting them wastes developer time:
.clone() in tests — Clarity over performance in test codeunwrap() in tests and examples — Acceptable where panicking on failure is intentionalBox<dyn Error> in simple binaries — Not every application needs custom error typesString fields in structs — Owned data in structs is correct; &str fields require lifetime parameters#[allow(dead_code)] during development — Common during iterationtodo!() / unimplemented!() in new code — Valid placeholder during active development.expect("reason") with clear message — Self-documenting and acceptable for invariantsuse super::* in test modules — Standard pattern for #[cfg(test)] modulestype Result<T> = std::result::Result<T, MyError> is idiomaticimpl Trait in return position — Zero-cost abstraction, standard patterncollect::<Vec<_>>() is idiomatic when type inference needs help_ prefix for intentionally unused variables — Compiler convention#[expect(clippy::...)] with justification — Self-cleaning lint suppressionArc::clone(&arc) — Explicit Arc cloning is idiomatic and recommendedstd::sync::Mutex for short critical sections in async — Tokio docs recommend thisfor loops over iterators — When early exit or side effects are neededasync fn in trait definitions — Stable since 1.75; async-trait crate only needed for dyn Trait or pre-1.75 MSRVLazyCell / LazyLock from std — Stable since 1.80; replaces once_cell and lazy_static for new code+ use<'a, T> precise capture syntax — Edition 2024 syntax for controlling RPIT lifetime captureOnly flag these issues when the specific conditions apply:
| Issue | Flag ONLY IF |
|---|---|
| Missing error context | Error crosses module boundary without context |
Unnecessary .clone() | In hot path or repeated call, not test/setup code |
| Missing doc comments | Item is pub and not in a #[cfg(test)] module |
unwrap() usage | In production code path, not test/example/provably-safe |
Missing Send + Sync | Type is actually shared across thread/task boundaries |
| Overly broad lifetime | A shorter lifetime would work AND the API is public |
Missing #[must_use] | Function returns a value that callers commonly ignore |
Stale #[allow] suppression | Should be #[expect] for self-cleaning lint management |
Missing Copy derive | Type is ≤24 bytes with all-Copy fields and used frequently |
Edition 2024: ! type fallback | Match on Result<T, !> or diverging expressions where () fallback was assumed — ! now falls back to ! not () |
Edition 2024: r#gen identifier | Code uses gen as an identifier — must be r#gen in edition 2024 (reserved keyword) |
Satisfy Gates § verification protocol (step 4). Load and follow beagle-rust:review-verification-protocol before reporting any issue.