Skip to content

S06: &String, &Vec<T>, &Box<T> parameters

S06Severity: Low

A reference to the container where a slice (&str, &[T]) or &T is more flexible.

Clippy: ptr_arg, borrowed_box

pub fn total_cents(orders: &Vec<Order>) -> u64 {
orders.iter().map(|o| o.total_cents).sum()
}
pub fn is_vip(customer: &String) -> bool {
customer.starts_with("VIP-")
}
pub fn describe(order: &Box<Order>) -> String {
format!("order {} for {}", order.id, order.customer)
}

And callers that must build a Vec or a String only to call the functions:

let first_two = orders[..2].to_vec(); // copy, only to get a `Vec`
let total = total_cents(&first_two);
let vip = is_vip(&"VIP-Ada".to_string()); // allocate, only to get a `String`
  • It is a direct translation. A Java List<Order> parameter becomes &Vec<Order>, and a String parameter becomes &String. The types look like the Java ones.
  • The compiler suggests it. When a function takes Vec<Order> and the caller passes &orders, the error message suggests “consider borrowing here”. The agent changes the parameter to &Vec<Order>, and stops there.
  • Callers have fewer options. A &[Order] parameter accepts a &Vec<Order>, an array, and a part of a list (&orders[..2]). A &Vec<Order> accepts only a Vec. The same holds for &str (literals, slices, and &String) vs &String.
  • Callers copy to fit the type. In the example, orders[..2].to_vec() copies two orders, and "VIP-Ada".to_string() allocates, only to call a function that reads.
  • It adds a level of indirection. &Vec<T> is a pointer to a pointer to the data. &[T] points to the data directly. The cost is small, but it is a cost for nothing.
  • &Box<T> is never necessary. Box<T> derefs to T, so &Order accepts a boxed order too. A &Box<T> parameter also tells the reader something false: that the function cares that the value is on the heap.
  1. WorseEach parameter names a container: &Vec, &String, &Box.

    pub fn total_cents(orders: &Vec<Order>) -> u64 {
    orders.iter().map(|o| o.total_cents).sum()
    }
    pub fn is_vip(customer: &String) -> bool {
    customer.starts_with("VIP-")
    }
    pub fn describe(order: &Box<Order>) -> String {
    format!("order {} for {}", order.id, order.customer)
    }
  2. BetterEach parameter names the data: &[Order], &str, &Order.

    pub fn total_cents(orders: &[Order]) -> u64 {
    orders.iter().map(|o| o.total_cents).sum()
    }
    pub fn is_vip(customer: &str) -> bool {
    customer.starts_with("VIP-")
    }
    pub fn describe(order: &Order) -> String {
    format!("order {} for {}", order.id, order.customer)
    }

The function bodies do not change. Callers now pass what they have:

let total = total_cents(&orders[..2]); // a slice of the list, no copy
let vip = is_vip("VIP-Ada"); // a literal works
let text = describe(boxed); // works for `&Box<Order>`, `&Order` and `&Rc<Order>`
  • &mut Vec<T> and &mut String are correct when the function adds or removes elements. A slice cannot grow:
/// `&mut Vec` is correct here: the function adds elements, and a slice
/// cannot grow.
pub fn add_free_sample(orders: &mut Vec<Order>, customer: &str) {
let next_id = orders.iter().map(|o| o.id).max().unwrap_or(0) + 1;
orders.push(Order::new(next_id, customer, 0));
}
  • When the function needs a method that only Vec has, such as capacity(). This is rare in a read-only function.
  • In a private helper that is only called with one type. It is still worth fixing, but it is a low priority.
  • Clippy ptr_arg (style group, on by default) reports &Vec<T>, &String, and &PathBuf parameters. Clippy borrowed_box (complexity group, on by default) reports &Box<T>.
  • Search for : &Vec<, : &String, : &Box< and : &PathBuf in function signatures.
  • In a review, look at the caller: .to_vec() or .to_string() right before a call that only reads is a sign.
Change the read-only parameters to the borrowed general types: &Vec<Order>
to &[Order], &String to &str, &Box<Order> to &Order, &PathBuf to &Path.
Keep &mut Vec only where the function adds or removes elements. Then remove
the .to_vec() and .to_string() calls that callers made only to fit the old
types. Run cargo clippy and fix every ptr_arg and borrowed_box warning.

Related: S05: Owned parameters (String, Vec<T>) where a borrow is sufficient, and Borrowing.