S01: clone() to satisfy the borrow checker
What it looks like
Section titled “What it looks like”pub fn total_cents(orders: Vec<Order>) -> u64 { orders.iter().map(|o| o.total_cents).sum()}
pub fn print_report(orders: &[Order]) -> String { let total = total_cents(orders.to_vec()); // copies every order format!("{} orders, {} cents", orders.len(), total)}
pub fn monthly_report(orders: Vec<Order>) -> (u64, String) { let total = total_cents(orders.clone()); // fixed E0382 with a copy let report = print_report(&orders); (total, report)}total_cents takes Vec<Order>, so each caller must give it a whole list that it owns. The callers make one with .clone() and .to_vec().
Why AI agents produce it
Section titled “Why AI agents produce it”The first version of monthly_report did not compile:
fn total_cents(orders: Vec<Order>) -> u64 { orders.iter().map(|o| o.total_cents).sum()}
fn monthly_report(orders: Vec<Order>) { let total = total_cents(orders); // `orders` moves into the function print_report(&orders); // error E0382: borrow of moved value}The compiler says “borrow of moved value: orders”. The smallest change that compiles is total_cents(orders.clone()). The agent makes that change and reports success. The real problem, that total_cents asks for ownership it does not need, stays.
Why it is a problem
Section titled “Why it is a problem”- Cost. Each clone allocates and copies the whole list. In a loop or on a large collection this is real work and memory, for a read-only operation.
- Unclear intent. A reader cannot tell if the copy is necessary (the callee changes it, or keeps it) or only a workaround.
- It hides the design error. The signature
total_cents(orders: Vec<Order>)says “I consume your orders”. That is false.
The better version
Section titled “The better version”Change the function to borrow, then remove the clones:
WorseTakes
Vec<Order>and makes every caller copy the list.pub fn total_cents(orders: Vec<Order>) -> u64 {orders.iter().map(|o| o.total_cents).sum()}pub fn print_report(orders: &[Order]) -> String {let total = total_cents(orders.to_vec()); // copies every orderformat!("{} orders, {} cents", orders.len(), total)}pub fn monthly_report(orders: Vec<Order>) -> (u64, String) {let total = total_cents(orders.clone()); // fixed E0382 with a copylet report = print_report(&orders);(total, report)}BetterTakes
&[Order]. Callers lend the list; nothing is copied.pub fn total_cents(orders: &[Order]) -> u64 {orders.iter().map(|o| o.total_cents).sum()}pub fn print_report(orders: &[Order]) -> String {format!("{} orders, {} cents", orders.len(), total_cents(orders))}pub fn monthly_report(orders: &[Order]) -> (u64, String) {(total_cents(orders), print_report(orders))}
&[Order] is a borrowed slice. It accepts a &Vec<Order>, an array, or part of a list. See Borrowing.
When it is acceptable
Section titled “When it is acceptable”-
ArcandRc. Cloning anArc<T>orRc<T>copies a pointer and increments a counter. The data is not copied. This is the normal way to share data with a thread:pub fn spawn_total(orders: &Arc<Vec<Order>>) -> thread::JoinHandle<u64> {let orders = Arc::clone(orders); // one more owner; the orders are not copiedthread::spawn(move || orders.iter().map(|o| o.total_cents).sum())}Many teams write
Arc::clone(&orders)instead oforders.clone()to show that the clone is cheap. -
The callee must own the data. For example, it stores the value in a struct, sends it to another thread, or changes it while the caller keeps the original.
-
Small data that is not on a hot path. Cloning a short
Stringonce at startup costs nothing that matters.
See Clone and Copy.
How to detect it
Section titled “How to detect it”- Clippy
needless_pass_by_value(pedantic) flags a function that takes ownership of a value it only reads. Fix that, and the clones at the call sites become unnecessary. - Clippy
redundant_clone(nursery) flags a clone whose original is not used again. - In review, search the diff for
.clone(),.to_vec(),.to_owned(), and.to_string()next to a function call. Ask: “Does the callee need to own this?” - In the agent’s log, a new
.clone()right after an E0382 or E0505 error is a strong signal.
What to tell your agent
Section titled “What to tell your agent”In monthly_report and print_report, remove the .clone() and .to_vec() calls.Change total_cents to take orders: &[Order] instead of Vec<Order>.Do not add clones to fix borrow errors; change the function signatures instead,and explain any clone that remains.Related: S05: Owned parameters (String, Vec<T>) where a borrow is sufficient.