Skip to main content

rust-clone-audit

Detect and eliminate abusive cloning in Rust: treat every `.clone()`, `.to_owned()`, `.to_vec()`, `Cow::into_owned()`, and equivalent owned-duplication call as a potential ownership-design defect, not a free fix for a borrow-checker error. Use when writing, reviewing, or refactoring Rust code, especially when a `.clone()` is added to make code compile.

Source facts

Repository
cosmian/kms
Last source activity
October 10, 2026 at 21:50
Detected SKILL.md language
English
Stars
367
Forks
43

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
rust-clone-audit
description
Detect and eliminate abusive cloning in Rust: treat every `.clone()`, `.to_owned()`, `.to_vec()`, `Cow::into_owned()`, and equivalent owned-duplication call as a potential ownership-design defect, not a free fix for a borrow-checker error. Use when writing, reviewing, or refactoring Rust code, especially when a `.clone()` is added to make code compile.
# Rust Clone Audit Companion to [`rust-signature-design`](../rust-signature-design/SKILL.md) (parameter identity/count) and [`rust-refactor`](../rust-refactor/SKILL.md) (duplicated code blocks): this skill targets a different axis — the **owned-vs-borrowed decision for a single value** at the call site where a clone-equivalent call appears. It does not scan for duplicated logic or judge parameter lists. Goal: identify and aggressively eliminate unnecessary cloning. Must not introduce `.clone()` merely to satisfy the borrow checker without first exhausting borrowing, lifetime, and ownership-transfer alternatives — the same rule the lint-suppression decision tree in [`rust.instructions.md`](../../instructions/rust.instructions.md) already applies to `clippy::redundant_clone` and `clippy::clone_on_copy`. ## Step 1 — Classify every clone-equivalent call | Kind | Call shape | Legitimate when | KMS example | | --- | --- | --- | --- | | Handle clone | `Arc::clone(&x)`, `Rc::clone(&x)`, `x.clone()` on `Arc`/`Rc` | Always — O(1) ref-count bump, not a data copy | `Arc::clone(&recorder.calls)` (`database_objects.rs:1131`), `Arc::clone(&barrier)` (`audit/pgsql.rs:2070`) | | Thread/task move clone | `x.clone()` right before `tokio::spawn`/`thread::spawn(move \|\| ...)` | The spawned task outlives the current scope and needs its own owned copy | — | | Copy-type clone | `.clone()` on a `Copy` type (`u32`, `bool`, small enum) | Never needed — use the value directly or `.copied()`/`.cloned()` on an `Option`/`Iterator` | — | | Borrow-checker-dodge clone | `.clone()` added only to make an error disappear, with no owned-value requirement downstream | Never — restructure the borrow instead (see rust-unofficial/patterns, "Clone to satisfy the borrow checker") | — | | Field-extraction clone | `self.field.clone()` returned as an owned value from `&self` | The caller genuinely needs ownership (stores it, moves it across an `await`/thread boundary); otherwise return `&T`/`&str` | — | | API-forced clone | Function signature takes `T` but the body only reads it — every caller must `.clone()` to call it | Never by itself — change the parameter to `&T` (one signature change fixes every call site) | — | | Cow-eligible clone | `.to_string()`/`.to_owned()` used because *some* code paths modify the value and others don't | Only on the paths that actually modify; otherwise return `Cow<'_, str>` or the borrow | — | ## Step 2 — Discover ```bash # Clone-equivalent call density per file, worst-first rg -c '\.clone\(\)|\.to_owned\(\)|\.to_vec\(\)|Cow::into_owned\(\)' --type rust <path> | sort -t: -k2 -rn | head -20 # Clones immediately preceding a borrow-checker-shaped error pattern: same binding re-read right after rg -n '(\w+)\.clone\(\)' --type rust <path> -B1 | rg -B1 'let \w+ = &?\1\b' # Clones on Copy-eligible primitives (near-certain waste) rg -n '\.(u8|u16|u32|u64|usize|i8|i16|i32|i64|isize|bool|f32|f64)\(\)\.clone\(\)|: *(u8|u16|u32|u64|usize|bool)\b.*\.clone\(\)' --type rust <path> # Functions that take an owned T but only read it (API-forced clone candidates) rg -n 'fn \w+\([^)]*: (String|Vec<\w+>|HashMap<[^)]+>)[^)]*\)' --type rust <path> ``` ## Step 3 — Pick the narrowest fix (checklist, in order) 1. **Can the parameter/field be borrowed instead?** Change `T` → `&T` (or `&str`/`&[T]` for `String`/`Vec<T>`); update every caller — this is often the single-signature fix that removes N call-site clones at once. 2. **Is this `Arc::clone`/`Rc::clone`?** Stop — it is already correct, leave it. 3. **Is the value moving into a spawned task/thread/`'static` closure?** Clone is necessary; leave it, optionally add a `// moved into spawned task` comment if the reason isn't obvious from context. 4. **Does only some code paths need an owned value?** Return `Cow<'_, T>` instead of unconditionally cloning or unconditionally borrowing. 5. **Is the clone on a `Copy` type?** Remove `.clone()` entirely; use `.copied()`/`.cloned()` for `Option<&T>`/`Iterator<Item = &T>` conversions. 6. **Is the clone structurally required** (stored in a new owner, e.g. a struct field or a `HashMap` key/value, with no shared-ownership type in use)? Keep it — this is legitimate duplication, not abuse. Do not force `Arc`/`Rc` onto a type with no concurrent/shared ownership need just to avoid a single clone. ## Before / after (illustrative) ```rust // Before — caller forced to clone because the function takes ownership it never needs. fn log_message(msg: String) { println!("[LOG] {msg}"); } log_message(message.clone()); log_message(message); // After — borrow; zero allocation, callable repeatedly. fn log_message(msg: &str) { println!("[LOG] {msg}"); } log_message(&message); log_message(&message); ``` ## Do not - Do not remove a clone that is structurally required (the value is stored in a new owner, or moved into a spawned task/thread) just to reduce a clone count. - Do not replace a single clone with `Arc`/`Rc`/`RefCell` wrapping across a type with no concurrent or shared-ownership requirement — that trades one allocation for a bigger runtime cost (atomic ops, interior-mutability panics) and a wider API change. - Do not silence `clippy::redundant_clone` / `clippy::clone_on_copy` with a bare `#[allow]` instead of fixing the call site — forbidden on new code per `rust.instructions.md`. - Do not change a `crate/kmip` wire struct's field from owned to borrowed — KMIP structs are spec-bound value types serialized independently of caller lifetimes. - Do not flag `Arc::clone`/`Rc::clone` as defects — they are O(1) ref-count bumps, never a data copy (see Step 1). ## References - rust-unofficial/patterns, ["Clone to satisfy the borrow checker"](https://github.com/rust-unofficial/patterns/blob/main/src/anti_patterns/borrow_clone.md) - Microsoft, ["Avoiding Excessive clone()"](https://microsoft.github.io/RustTraining/c-cpp-book/ch17-1-avoiding-excessive-clone.html) — source of the Step 1 "legitimate when" column and the before/after example - Rust Project Goals, ["Ergonomic ref-counting"](https://goals.rust-lang.org/2024h2/ergonomic-rc.html)
View on GitHub