Skip to content

S01: clone() to satisfy the borrow checker

S01Severity: Medium

A copy of the data is made only to make a borrow error go away.

Clippy: needless_pass_by_value, redundant_clone

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().

The first version of monthly_report did not compile:

Does not compile: E0382
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.

  • 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.

Change the function to borrow, then remove the clones:

  1. 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 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)
    }
  2. 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.

  • Arc and Rc. Cloning an Arc<T> or Rc<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 copied
    thread::spawn(move || orders.iter().map(|o| o.total_cents).sum())
    }

    Many teams write Arc::clone(&orders) instead of orders.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 String once at startup costs nothing that matters.

See Clone and Copy.

  • 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.
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.