Skip to content

S05: Owned parameters where a borrow is sufficient

S05Severity: Low

The function takes ownership of data that it only reads.

Clippy: needless_pass_by_value

pub fn greeting(name: String) -> String {
format!("Dear {name},")
}
pub fn count_large(orders: Vec<Order>, min_cents: u64) -> usize {
orders.iter().filter(|o| o.total_cents >= min_cents).count()
}

And at the call site:

let first = &orders[0];
let text = greeting(first.customer.clone()); // copy only to call the function
let large = count_large(orders.to_vec(), 10_000); // copy the whole list
  • Java habit. In Java, String name and List<Order> orders are always references, so the parameter type does not say anything about ownership. The agent writes the Rust type that “looks the same”.
  • It avoids lifetime questions. Owned types never need a lifetime annotation, so an agent that had a lifetime error once may use them everywhere.
  • Callers must copy. A caller that still needs the value after the call must .clone() it first (S01).
  • The signature says the wrong thing. fn greeting(name: String) reads as “this function keeps or consumes the name”. It only formats it.
  • Less flexible. &str accepts a String, a string literal, or part of a string. String accepts only a String.
  1. WorseTakes ownership of data that it only reads.

    pub fn greeting(name: String) -> String {
    format!("Dear {name},")
    }
    pub fn count_large(orders: Vec<Order>, min_cents: u64) -> usize {
    orders.iter().filter(|o| o.total_cents >= min_cents).count()
    }
  2. BetterBorrows a &str and a &[Order]. Callers keep their values.

    pub fn greeting(name: &str) -> String {
    format!("Dear {name},")
    }
    pub fn count_large(orders: &[Order], min_cents: u64) -> usize {
    orders.iter().filter(|o| o.total_cents >= min_cents).count()
    }

The call site no longer copies:

let first = &orders[0];
let text = greeting(&first.customer);
let large = count_large(orders, 10_000);

Use the borrowed form of the type: &str for String, &[T] for Vec<T>, &Path for PathBuf. See Borrowing and Strings.

When the function stores or consumes the value, taking ownership is correct. A constructor is the usual example:

pub struct Customer {
name: String,
orders: Vec<Order>,
}
impl Customer {
/// Takes ownership because the struct stores the values.
pub fn new(name: String, orders: Vec<Order>) -> Self {
Self { name, orders }
}
}

If the caller has a String that it no longer needs, it moves it in, and nothing is copied. Other correct cases: the function sends the value to another thread, or returns a changed version of it.

Some APIs take impl Into<String> or impl AsRef<str> to accept both forms. That is also fine.

  • Clippy needless_pass_by_value (pedantic) finds parameters that are taken by value but only borrowed in the body.
  • In review: for each String, Vec<T>, PathBuf, or HashMap parameter, ask “does the function keep it?” If not, it should borrow.
  • Look at the call sites. .clone() or .to_vec() in the arguments usually means the callee should borrow.
Change greeting to take name: &str and count_large to take orders: &[Order].
Remove the .clone() and .to_vec() at the call sites. Only take String or Vec
by value when the function stores or consumes the value.

Related: S01: clone() to satisfy the borrow checker and S06: &String, &Vec<T>, &Box<T> parameters.