Skip to content
Back to skills

Rust Conventions

ASecurity

Use when a ticket adds or changes Rust code and it must follow the repo's Rust conventions — ownership and borrowing done right, explicit error handling with Result and typed errors, no needless unsafe or clones, idiomatic traits and iterators per the Rust API Guidelines, async/tokio used without blocking or lock-across-await bugs, clippy- and rustfmt-clean — as the language pack for any Rust change. Invoke for "add this in Rust", "fix the borrow checker error", "remove the unwraps", or when ...

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 28, 2026
ai-agentsrustgoapidatabasesecurity

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add tmj-90/gaffer --skill rust-conventions --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rust Conventions?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Rust Conventions
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tmj-90-rust-conventions/badge)](https://www.skillsdirectory.com/skills/tmj-90-rust-conventions)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: rust-conventions
description: Use when a ticket adds or changes Rust code and it must follow the repo's Rust conventions — ownership and borrowing done right, explicit error handling with Result and typed errors, no needless unsafe or clones, idiomatic traits and iterators per the Rust API Guidelines, async/tokio used without blocking or lock-across-await bugs, clippy- and rustfmt-clean — as the language pack for any Rust change. Invoke for "add this in Rust", "fix the borrow checker error", "remove the unwraps", or when reviewing a Rust diff.
stack: [rust]
area: language
---

# Write idiomatic, safe Rust

Rust rewards code that makes ownership and failure explicit and punishes code that
fights the compiler with clones, `unwrap` and `unsafe`. For the builder and the reviewer of a Rust diff; the crate's config and existing code win over it.

## Procedure

1. **Discover the crate's conventions first.** Call `search_lore` for Rust conventions.
   Read `Cargo.toml` (`edition`, `rust-version` MSRV, `[lints]`/`[workspace.lints]`,
   features), `rust-toolchain.toml`, `clippy.toml`, `rustfmt.toml`, `deny.toml`,
   `.cargo/config.toml` and the CI workflow. Identify the error strategy (`thiserror`
   enums in libraries, `anyhow` at the binary edge, or hand-rolled), the async runtime,
   and the policy on `unsafe` (`#![forbid(unsafe_code)]`?). Copy a sibling module and its
   tests.
2. **Pin the exact commands** from CI, `Makefile`/`justfile` or `xtask` (the `run-tests`
   and `run-lint` skills). Use the defaults below only when the repo defines none; match
   CI's feature flags.
3. **Write the change with the idioms below**, then walk the concurrency section for
   every task, lock, channel, file and outbound call you touched.
4. **Test each acceptance criterion's own behaviour.** One test per AC that fails without
   your change, plus its error path. If the AC involves shared state or persistence, add
   a test that spawns N threads or tasks against it and asserts the invariant; use
   `proptest` for wide inputs (the `property-based-test` skill).
5. **Verify, then stop.** Done when: `cargo fmt --check` and clippy are clean at CI's
   level, tests pass with CI's features, public items are documented, and every AC has a
   test. Record the output with the `record-evidence` skill; the runner submits the work.

## Commands

- Format: `cargo fmt --all -- --check`.
- Lint: `cargo clippy --all-targets --all-features -- -D warnings` (or CI's feature set).
- Test: `cargo test --all-features` (or `cargo nextest run` where configured; nextest
  skips doctests, so also run `cargo test --doc`); one test: `cargo test name -- --exact`.
- Docs: `cargo doc --no-deps` with `RUSTDOCFLAGS="-D warnings"` if CI sets it.
- Dependencies: never `cargo add`/`cargo update`; a new or bumped crate is a blocker (the
  `dependency-upgrade` skill). `cargo deny check` where CI runs it.
- Unsafe: `cargo +nightly miri test` for code with `unsafe`, only if the repo uses Miri
  and the nightly toolchain is already installed.

## Idioms that matter

- **Types carry invariants**: newtypes for ids and units, enums for closed sets, `Option`
  for absence, constructors that validate so invalid states cannot be built.
- **Borrow in, own out**: take `&str`, `&[T]`, `impl AsRef<Path>`; return owned values;
  restructure instead of `clone()` to appease the borrow checker; `Arc` only when sharing
  is real.
- **Errors**: `Result` and `?`; library errors are typed (`thiserror`), implement
  `std::error::Error + Send + Sync + 'static`, and carry context; `anyhow::Context` at the
  binary edge. `unwrap`/`expect` only for proven-impossible states, with the invariant in
  the `expect` message; never panic on input from users, files or the network.
- **API Guidelines naming**: `as_` (cheap borrow), `to_` (costly conversion), `into_`
  (consuming); `iter`/`iter_mut`/`into_iter`; derive the common traits (`Debug`, `Clone`,
  `PartialEq`, `Default` where meaningful); document `# Errors`, `# Panics` and `# Safety`
  on public functions.
- **Iterators and pattern matching** over index loops; `let … else` for early return;
  generics/`impl Trait` over `dyn` unless dynamic dispatch is needed.
- **Integer arithmetic** on untrusted values uses `checked_`/`saturating_` methods (release
  builds wrap silently); `as` casts that can truncate use `try_from`.
- **`unsafe`** only with a `// SAFETY:` comment proving each invariant and a test.

## Concurrency and resource safety

- **No blocking in async**: file I/O, `std::thread::sleep`, CPU-heavy work and sync
  clients go through `tokio::task::spawn_blocking` or async equivalents.
- **No lock guard across `.await`**: holding a `std::sync::Mutex` guard over an await can
  deadlock and makes the future `!Send` (clippy `await_holding_lock`). Scope the guard
  so it drops first, or use `tokio::sync::Mutex` when the lock must span the await.
- **Read-modify-write in one critical section**: read, compute and write under the same
  guard, or use atomics/`compare_exchange`; releasing between read and write loses updates.
- **`tokio::select!`** drops the losing branches: only use cancel-safe futures there
  (the tokio docs list which methods are), or pin the future outside the loop.
- **Tasks have owners**: a dropped `JoinHandle` does not cancel the task; keep handles in a
  `JoinSet` or abort them, propagate shutdown with a `CancellationToken`, and bound channels.
- **Files other requests read**: `std::fs::write` is not atomic. Use
  `tempfile::NamedTempFile::new_in(dir)` (unique, same filesystem), write, `sync_all`,
  then `persist(target)`. Flush `BufWriter` explicitly: its `Drop` swallows write errors.
  Cross-process exclusion needs an OS lock or the database; a time-only lease lets a paused
  holder write late, so writes must check a fencing token or version.
- **`RefCell`/`RwLock` re-entry** panics or deadlocks; never re-borrow inside a borrow.

## Review checklist — flag as defects

Walk this against the diff. An item is grounds for CHANGES only when, in changed code, it
causes a concrete failure (wrong result, crash, lost or corrupted data, security hole) or
leaves an AC's own behaviour untested: cite the line and that failure. Otherwise it is an
`(optional)` note. Formatting the tools would fix, and preferences the crate
does not enforce, are not findings. Do not patch the code under review.

- [ ] `unwrap`/`expect`/indexing/`panic!` reachable from external input.
- [ ] An error is discarded (`let _ =` on a `Result`, `.ok()` dropping the cause) or
      stringly typed where callers need to match on it.
- [ ] A lock guard is held across `.await`; blocking I/O runs on the async runtime.
- [ ] A read-modify-write releases its lock between read and write; shared state is
      mutated without synchronisation through `unsafe` or interior mutability.
- [ ] A non-cancel-safe future sits in `select!`; a spawned task is detached with no
      shutdown path; a channel is unbounded on user-driven input.
- [ ] A shared file is written in place or through a fixed temp name; `BufWriter` is not
      flushed; a lock relies on a time-only lease.
- [ ] `unsafe` without a `// SAFETY:` proof and a test.
- [ ] Unchecked arithmetic or truncating `as` on untrusted values.
- [ ] A new `#[allow(clippy::…)]` without a reason, or lints loosened in config.
- [ ] An AC has no test, or the test does not exercise the AC's code path.

## Capture lore

This skill is one of the places durable, reusable knowledge naturally surfaces:
**While matching the crate you learn its error strategy, its async runtime and its policy on unsafe.** That kind of fact is *lore*. Capture it via the **lore-capture
protocol in your brief** (`CLAUDE.factory.md`, step 11 "Memory contribution"):
call the Memory MCP `suggest_lore` once at the close of your work — reusable
conventions, gotchas, decisions, and boundaries only, never per-ticket trivia.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…