| name | rust-refactor |
| description | Find duplication in Rust code and consolidate it using Traits, Generics, shared functions, and macros. Use when asked to reduce Rust code duplication. |
Rust Refactor
Find opportunities to reduce LOC and consolidate duplicated behavior using Traits, Generics, shared functions, and macros.
Workflow
Phase 1 — Discover (always run first)
rg "\.clone\(\)" --stats | tail -5
rg -l "fn create_" crate/server/src/core/operations/
Run the Duplication Scan checklist:
Phase 2 — Classify each finding
For each duplicate, pick the minimum sufficient Rust pattern:
| Smell | Pattern |
|---|
| Same logic on N types | Generic function / impl<T: Bound> |
| Same interface, different impls | Trait + impl Trait for Type |
Repetitive match / boilerplate impls | Declarative macro (macro_rules!) |
| Cross-cutting behaviour (logging, auth) | Blanket impl or extension trait |
| Repeated struct field group | Composition / newtype |
Repeated From/Into conversions | Derive macro or impl From<X> for Y |
| Repeated KMIP operation boilerplate | macro_rules! dispatch helpers |
Phase 3 — Plan
Create a ranked list ordered by: impact (lines saved) ÷ risk (tests affected).
Present it to the user before touching code:
[HIGH] Extract `KeyOperations` trait — saves ~120 LOC, touches 3 files
[MED] Generic `retrieve<T: KmipObject>` — saves ~60 LOC, touches 2 files
[LOW] `macro_rules! impl_kmip_response!` — saves ~30 LOC, touches 5 files
Phase 4 — Implement (one finding at a time)
- Create the trait / generic / helper in its own commit scope.
- Migrate callers one file at a time; keep the build green after each file.
- Delete the old code only after all callers compile.
- After each change:
cargo clippy-all and cargo fmt --all.
- Run the narrowest test scope that covers the changed code:
cargo test -p <crate>.
Phase 5 — Verify
cargo clippy-all
cargo fmt --all
cargo test -p <crate>
git diff --stat
Quick Rules
- Prefer generics over
dyn Trait unless the set of types is open at runtime.
- Add
#[allow(clippy::...)] only with an inline comment explaining why the lint cannot be satisfied.
- Never remove a public function without checking all crates:
rg "fn_name" --type rust.
- Keep functions under 50 lines after refactoring. Extract helpers rather than growing functions.
- Imports (
use statements) always go at the top of the file — never inline inside function bodies.
- Feature-flag gating stays at function/module level, not inline:
#[cfg(feature = "non-fips")] on the fn, not inside the body.
Pattern Cookbook
Generic function over KMIP objects
async fn get_object(uid: &str, db: &dyn Database) -> KResult<KmipObject> { ... }
async fn export_object(uid: &str, db: &dyn Database) -> KResult<KmipObject> { ... }
async fn retrieve_object<P: Permission>(
uid: &str,
caller: &str,
db: &dyn Database,
) -> KResult<KmipObject>
where P: CheckAccess { ... }
Declarative macro for KMIP dispatch boilerplate
macro_rules! dispatch_operation {
($op:expr, $kms:expr, $params:expr) => {
match $op {
Operation::Get(req) => get::get($kms, req, $params).await,
Operation::Locate(req) => locate::locate($kms, req, $params).await,
}
};
}
Newtype for key identifiers
fn get_key(uid: String, owner: String) -> ...
struct KeyUid(String);
struct UserId(String);
fn get_key(uid: KeyUid, owner: UserId) -> ...