S06: &String, &Vec<T>, &Box<T> parameters
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 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`Why AI agents produce it
Section titled “Why AI agents produce it”- It is a direct translation. A Java
List<Order>parameter becomes&Vec<Order>, and aStringparameter 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.
Why it is a problem
Section titled “Why it is a problem”- 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 aVec. 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 toT, so&Orderaccepts 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.
The better version
Section titled “The better version”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)}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 copylet vip = is_vip("VIP-Ada"); // a literal workslet text = describe(boxed); // works for `&Box<Order>`, `&Order` and `&Rc<Order>`When it is acceptable
Section titled “When it is acceptable”&mut Vec<T>and&mut Stringare 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
Vechas, such ascapacity(). 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.
How to detect it
Section titled “How to detect it”- Clippy
ptr_arg(style group, on by default) reports&Vec<T>,&String, and&PathBufparameters. Clippyborrowed_box(complexity group, on by default) reports&Box<T>. - Search for
: &Vec<,: &String,: &Box<and: &PathBufin function signatures. - In a review, look at the caller:
.to_vec()or.to_string()right before a call that only reads is a sign.
What to tell your agent
Section titled “What to tell your agent”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 removethe .to_vec() and .to_string() calls that callers made only to fit the oldtypes. 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.