Skip to content
Back to skills

Refactor Rust

ASecurity

Rust norms: borrowing over cloning, newtypes over primitive obsession, module layout that mirrors the domain and no panics on a public path. Use when editing Rust files.

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 1, 2026
ai-agentsrustgorefactoringapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 1, 2026

npx -y skills add Univers42/claude-deal-with-the-devil --skill refactor-rust --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Refactor Rust?

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

Security grade badge for Refactor Rust
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/univers42-refactor-rust-claude-deal-with-the-devil/badge)](https://www.skillsdirectory.com/skills/univers42-refactor-rust-claude-deal-with-the-devil)

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: refactor-rust
description: >
  Rust norms: borrowing over cloning, newtypes over primitive obsession, module layout
  that mirrors the domain and no panics on a public path. Use when editing Rust files.
paths:
  - "**/*.rs"
user-invocable: false
metadata:
  stage: rule
  since: "1.0.0"
---

# Rust Refactoring

## Idioms

- Max 40 lines per function. Max 4 parameters; beyond that, a struct with a builder
  only if the builder earns its rung (`rules/minimalism-ladder.md`).
- Borrow by default. Take `&str` not `String`, `&[T]` not `Vec<T>`, and return owned
  values only when the caller needs ownership.
- `impl Trait` in argument position for simple generic bounds; a named generic when the
  caller must be able to name the type.
- Derive rather than implement: `Debug`, `Clone`, `PartialEq`, `Default` — hand-written
  versions drift from the fields.
- Newtypes over primitive obsession: `struct UserId(u64)` makes the wrong argument a
  compile error.
- Modules mirror the domain, not the layer. `pub(crate)` by default; `pub` is a
  contract (the `api-convention` skill).

## Errors are types, not strings

- Libraries define an error enum with `thiserror`; binaries use `anyhow` at the edge.
  Do not make a library return `anyhow::Error` — it erases the match.
- `?` for propagation. Never `.unwrap()` or `.expect()` on a path that can fail at
  runtime; in tests and in `main` they are fine, and `expect` there carries the reason.
- `panic!` means a bug in this code, never bad input from outside.
- No `unwrap_or_default()` where the default silently hides a failure.

## Ownership is the memory model — do not fight it

- **Do not hand-roll pools or arenas to satisfy the borrow checker.** A lifetime
  problem is a design problem; restructure the ownership first
  (`rules/dsa-and-memory.md`).
- Reach for `Rc`/`RefCell` only when the data genuinely has shared ownership, and
  `Arc`/`Mutex` only across threads. Each one is a rung; state why.
- An arena (`bumpalo`, `typed-arena`) is rung 6: only after a profiler shows allocation
  is the bottleneck.
- `clone()` is not a failure. An unnecessary clone in a hot loop is — and the profiler
  says which.

## Unsafe

- `unsafe` requires a `// SAFETY:` comment stating the invariant the caller must uphold
  and why it holds here. No comment, no `unsafe`.
- Keep the block as small as the operation. Wrap it in a safe API and test that API.
- `cargo miri test` on any module containing `unsafe`.

## Async

- Do not hold a `std::sync::Mutex` guard across an `.await` — use `tokio::sync::Mutex`
  or restructure so the lock is released first.
- No blocking IO inside an async fn; `spawn_blocking` for it.
- Every spawned task's `JoinHandle` is awaited or deliberately detached with a comment.
- Prefer structured concurrency (`JoinSet`, scoped tasks) over detached tasks nobody
  owns.

## After refactoring

- `cargo fmt --check` — clean.
- `cargo clippy --all-targets --all-features -- -D warnings` — zero. Consider
  `-W clippy::pedantic` and silence individually with a reason.
- `cargo test` and `cargo test --release` (debug assertions hide overflow panics).
- `cargo audit` / `cargo deny check` — no known-vuln dependency (`rules/quality-bar.md`).
- `#![deny(missing_docs)]` on a published crate; doctests count as tests and run in CI.

## Rust-specific ladder notes

- Rung 2: `std::iter` chains, `slice::binary_search`, `entry()` API — before any crate.
- Rung 4: the project already has `serde`/`tokio`/`regex`? Use it. A second
  serialization or runtime crate is a fork in the codebase.
- Rung 7: a trait with one implementor is not an abstraction, it is indirection. Delete
  it and call the type.
- `#[allow(...)]` carries an issue link and a one-line reason, like any suppression.

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…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.