Clean rust
Some of my Agents & Skills, compatible with most AI coding tools
npx -y skills add uwuclxdy/agenticat --skill clean-rustAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Clean, idiomatic Rust 2024 conventions: ownership, error handling, async, unsafe, traits, iterators, performance. Use when writing or reviewing Rust, running clippy, or checking 'idiomatic rust'.
SKILL.md
10.1 KB, as published. Nobody here has run it
Clean Rust
Rust-specific conventions for writing, reviewing, and refactoring. The core rules below always apply. Load the one reference file matching the task's domain; don't load them all.
If the clean-code skill is installed, its language-agnostic principles (function size, naming hygiene, comment discipline) still apply. Where generic advice conflicts with Rust idiom, this skill wins. Classic examples: "prefer exceptions over error codes" maps to Result, never panics; "replace switch with polymorphism" maps to exhaustive match, which in Rust is a feature, not a smell.
| Task touches | File |
|---|---|
| Error types, propagation helpers, failure semantics, retries | references/error-handling.md |
async/await, tokio, select!, channels, spawned child processes | references/async.md |
| Threads, atomics, lock ordering, drop order | references/concurrency.md |
| Process-global mutable state (env vars, global overrides) across threads | references/edition-2024.md |
| Public API shape, builders, newtypes, typestate, serde | references/api-design.md |
Any unsafe block, FFI, raw pointers, exported symbols | references/unsafe.md |
| Writing tests or debugging test infrastructure | references/testing.md |
| Hot paths, allocations, string building, hasher choice | references/performance.md |
Benchmarks, criterion, #[bench] | references/performance.md |
| Secret files on disk, keys and tokens at rest | references/security.md |
| Logging, tracing spans, log levels | references/observability.md |
| Edition 2024 migration or 2024-specific behavior changes | references/edition-2024.md |
Errors
Result+?everywhere; no.unwrap()/.expect()outside tests and one-time init of hand-audited literals (LazyLock<Regex>on a fixed pattern). The rare justifiedexpectcarries a message naming the invariant that makes it safe.- No sentinel returns (
-1,"") for failure.Option<T>for absence that isn't an error. thiserrorfor library errors (callers can match),anyhow/eyreat the application boundary. NeverBox<dyn Error>orStringas a library error type.- Bind a
Resultonce withmatch/if let/let-else, never.is_ok()followed by separate access or a downstream.unwrap(). - Pick one failure semantics per operation: best-effort (warn per item,
Ok(())at end) or fail-fast (propagate first error). Never warn-per-item and then fail at the end; that surprises exit-code consumers.
Ownership & Borrowing
- Borrow by default:
&str,&[T],impl AsRef<Path>for read-only params. Never&Stringor&Vec<T>. - Every
.clone()is an explicit cost, never one to silence the borrow checker.Cow<'_, str>when allocation is conditional;Arc<T>for shared ownership across threads. - Unsigned types for counts, ports, sizes:
jobs: u32, notjobs: i32. The type documents the constraint. - Let lifetime elision work. A struct with 3+ lifetime params is a design smell: consider owned data.
Naming
RFC 430 casing (snake_case items, CamelCase types, SCREAMING_SNAKE_CASE consts) plus semantic method prefixes:
| Prefix | Contract |
|---|---|
into_ | consumes self, returns owned |
as_ | cheap borrowed view, no allocation |
to_ | possibly expensive, may allocate |
try_ | fallible variant returning Result |
is_ / has_ | boolean query |
with_ | builder-style configuration |
_mut suffix | mutable variant |
- No
get_for plain field access:fn name(&self) -> &str. Reservegetfor fallible lookups (get(key) -> Option<&V>). - Concrete names:
cmdnotexecutor,typnotty. Single letters only in tiny scopes. - Names must not lie about the data: no
_listsuffix on aHashMap, noowned_on a&str. - Wrap semantically distinct primitives in newtypes (
UserId(u64),OrderId(u64)) so the compiler catches argument swaps.
Control Flow & Iterators
matchon enums stays exhaustive: no_ => {}arm in dispatchers. Spell out every variant: an empty arm with a comment beats a wildcard that silently swallows the next variant added.let-elsefor guard-style early returns; it reads linearly where.ok().and_then(...).is_some_and(...)chains don't.- Iterator chains for value-producing pipelines. When the result is discarded (
let _ = ...), the chain is control flow in disguise; write theforloop. - Return
impl Iterator<Item = T>when callers consume sequentially; premature.collect()allocates for nothing. - Group one logical filter into one
.filter_map()closure (using?and earlyreturn None) instead of fragmented.filter_map().filter().filter_map()chains. - Don't reimplement the standard library:
split_once('=')over.splitn(2, '=').collect(),unwrap_or_default()overunwrap_or_else(|| T::default()). - One fluent chain over three named single-use intermediates; declare variables next to first use, not at the top of the function.
Types & Traits
- Derive liberally where semantically correct:
Debugon effectively every public type;Clone,PartialEq,Eq,Hashas meaning allows (ifa == bthenhash(a) == hash(b)must hold);Defaultwhen a meaningful empty value exists. dyn Traitonly for genuinely open sets (plugins, user extension). A closed set of a few known types is an enum: simpler and faster.- Typed
#[derive(Deserialize)]structs overserde_json::Valueindexing: field typos become compile errors instead of silent runtimeNones. impl Traitin argument position for simple bounds;whereclauses when signatures grow. Don't make a function generic when one concrete type is ever used.
Platform & cfg
- Short forms:
#[cfg(windows)],#[cfg(unix)], not#[cfg(target_os = "windows")]. - No redundant
#[cfg]inside a module that's already cfg-gated at itsmoddeclaration. - Helpers used on only one platform must be gated (or
#[allow(unused)]) so every target compiles warning-free. Cross-platform CI fails on the platform that doesn't use them.
Strings & IO
static RE: LazyLock<Regex>for compiled-once regexes; never compile inside a loop or per call.- ASCII character classes (
[a-zA-Z0-9]) over POSIX[[:alnum:]]: the explicit range reads unambiguously and ports to engines where POSIX classes are locale-sensitive (in Rust'sregexthey're ASCII-only either way). - Inline format args:
debug!("found {name:?}"), notdebug!("found {:?}", name). - Forward child-process output with
io::stdout().write_all(&output.stdout)?;println!mangles encoding and panics on broken pipes. - Long-form flags when spawning external commands (
--force, not-f): the call site is its own documentation. - Never truncate a
Stringat a raw byte index.&s[..n]panics mid-codepoint on non-ASCII; the "safe"s.get(..n).unwrap_or(s)is worse: it returns the whole string whennlands mid-codepoint, so oversize input escapes the cap. Usefloor_char_boundary(n)or walkchar_indices().
Modules & Visibility
- Private by default;
pub(crate)for internal sharing. Fields with invariants stay private behind methods. - Organize by domain (
order/,user/), not by kind (models/,services/). pubitems before private helpers in a file;usestatements at the top, never inside functions.
Comments & Docs
- Comments explain why: hidden constraints, upstream bug links, invariants the reader can't see. Never what the next line does. Delete commented-out code; git remembers.
- Public APIs get
///docs with# Examples(runnable), plus# Errors,# Panics,# Safetywhen applicable. - Every lint suppression is justified:
#[expect(lint, reason = "...")]over bare#[allow]. - Non-obvious string input formats get documented above the signature (
// accepts "name", "name:tag", or "ns/name:tag").
Tooling
cargo fmtandcargo clippyclean in CI, no exceptions. Fix warnings; don't blanket-suppress.- Libraries:
#![warn(clippy::pedantic)]and selectively allow, never a blanket#![allow(clippy::all)]. Considerunwrap_used/expect_usedatwarn(seereferences/testing.mdfor keeping test code exempt). - No formatting-only churn in feature PRs: the diff should match the description.
Unsafe
Default posture: unsafe_code = "forbid" until a concrete need exists. When it does exist, every block carries a // SAFETY: comment and stays minimal; full discipline in references/unsafe.md.
Process
- One PR = one focused change; formatting fixes, new workflows, and features travel separately.
- A technically-correct change that breaks a user contract (CLI flags, config keys, env semantics) keeps the legacy path, ships the new one alongside, and milestones the removal for the next major. Warn only on genuine old-vs-new conflicts: users on only-old or only-new see nothing.
- Feature-detect external tools by parsing
--versionoutput; where that's unreliable, sniff for specific stderr messages. Either way, probe with plain.output()so a failed probe stays non-fatal.
Pre-Submit Checklist
- No
.unwrap()outside tests/compile-time constants; no.is_ok()+ separate access - Failure semantics consistent: best-effort or fail-fast, not mixed; cleanup always runs
- Borrows by default; no
&String/&Vec<T>; every.clone()intentional - Method prefixes honest (
into_/as_/to_/try_); newtypes for swappable primitives - No
_ => {}in enum dispatchers;let-elseover combinator gymnastics -
impl Iteratorover prematureVec; no stdlib reimplementations - Typed
DeserializeoverValue;dynonly for open sets -
#[cfg(windows)]short form; single-platform helpers gated for all-target CI -
LazyLockregexes; inline format args;write_allfor child output - Comments say why; suppressions carry
reason; no commented-out code - fmt + clippy clean; no formatting churn outside the change
- One focused change per PR; breaking changes keep the legacy path, removal milestoned