Question
I wrote some Rust code that accepts &String as a function parameter:
fn awesome_greeting(name: &String) {
println!("Wow, you are awesome, {}!", name);
}
I have also written functions that take references to Vec and Box values:
fn total_price(prices: &Vec<i32>) -> i32 {
prices.iter().sum()
}
fn is_even(value: &Box<i32>) -> bool {
**value % 2 == 0
}
I was told that using &String, &Vec<T>, or &Box<T> as function arguments is usually discouraged in Rust. Why is that considered a poor API choice, and what types should be used instead?
Short Answer
By the end of this page, you will understand why Rust APIs usually prefer &str over &String, &[T] over &Vec<T>, and &T over &Box<T>. You will learn how these choices make functions more flexible, easier to call, and more idiomatic in real Rust code.
Concept
In Rust, function parameters should usually describe what the function needs, not how the caller stores the data.
That is the main reason &String, &Vec<T>, and &Box<T> are often discouraged.
The core idea
These types are all owned container types:
Stringowns heap-allocated textVec<T>owns a growable list of valuesBox<T>owns a heap-allocated single value
When you write a function parameter like:
fn awesome_greeting(name: &String)
you are saying:
- the caller must specifically have a
String - and you only want a reference to that exact container type
But most functions do not actually need a String. They only need string data they can read. Rust already has a more general type for that:
&str
The same applies to vectors and boxed values:
Mental Model
Think of this like asking to read a book.
String,Vec<T>, andBox<T>are like specific kinds of containers: a hardcover book, a storage box, or a folder.&str,&[T], and&Tare like asking for the content itself: the text on the page, the list of items, or the value inside.
If you only need to read the content, it is too restrictive to say:
- "I will only accept a hardcover book"
when what you really mean is:
- "I just need the text"
That is what &String does: it asks for a specific owned string container.
That is what &str does better: it asks for readable string data.
So the mental model is:
- use owned types when you need ownership
- use borrowed view types when you only need access
- prefer the most general type that matches what your function actually needs
Syntax and Examples
Preferred function signatures
fn awesome_greeting(name: &str) {
println!("Wow, you are awesome, {}!", name);
}
fn total_price(prices: &[i32]) -> i32 {
prices.iter().sum()
}
fn is_even(value: &i32) -> bool {
*value % 2 == 0
}
Why these are better
&straccepts string slices andStringreferences through coercion&[i32]accepts vectors, arrays, and sub-slices&i32accepts a reference to an integer, including one stored inside aBox<i32>
Example usage
fn awesome_greeting(name: &str) {
println!("Wow, you are awesome, {}!", name);
}
(prices: &[]) {
prices.().()
}
(value: &) {
*value % ==
}
() {
= ::();
(&name);
();
(&name[..]);
= [, , ];
= [, , ];
(, (&prices_vec));
(, (&prices_array));
(, (&prices_vec[..]));
= ::();
= ;
(, (&boxed));
(, (&plain));
}
Step by Step Execution
Consider this example:
fn total_price(prices: &[i32]) -> i32 {
prices.iter().sum()
}
fn main() {
let items = vec![10, 20, 30];
let result = total_price(&items);
println!("{}", result);
}
Step by step
1. Create the vector
let items = vec![10, 20, 30];
items is a Vec<i32>. It owns the numbers.
2. Borrow it for the function call
total_price(&items)
&items is a reference to the vector, but the function expects .
Real World Use Cases
1. Processing text input
A logging or validation function usually only needs to read text:
fn validate_username(name: &str) -> bool {
!name.trim().is_empty() && name.len() >= 3
}
This can work with:
String- string literals
- slices of strings
2. Reading lists of data
A statistics function often only needs a sequence:
fn average(values: &[i32]) -> f32 {
let sum: i32 = values.iter().sum();
sum as f32 / values.len() as f32
}
This works with vectors, arrays, and parts of collections.
3. Utility functions over values
A function checking a number does not care whether the number lives on the stack or heap:
Real Codebase Usage
In real Rust codebases, developers usually design function parameters around capability.
Common patterns
Read-only text input
Use &str when the function only reads text.
fn log_message(message: &str) {
println!("LOG: {}", message);
}
Read-only sequence input
Use &[T] when the function only reads a list.
fn contains_zero(values: &[i32]) -> bool {
values.iter().any(|v| *v == 0)
}
Generic read-only value access
Use &T when the function only needs the value.
fn print_number(n: &i32) {
println!("{}", n);
}
Guard clauses and validation
Common Mistakes
Mistake 1: Taking &String when &str is enough
Broken design:
fn greet(name: &String) {
println!("Hello, {}", name);
}
Problem:
- cannot accept a string literal directly
- unnecessarily tied to
String
Better:
fn greet(name: &str) {
println!("Hello, {}", name);
}
Mistake 2: Taking &Vec<T> instead of a slice
Overly specific:
fn sum_items(items: &Vec<i32>) -> i32 {
items.iter().sum()
}
Problem:
- does not naturally express "any readable sequence"
- less flexible than a slice
Comparisons
| Need | Too specific | Preferred | Why |
|---|---|---|---|
| Read text | &String | &str | Accepts more kinds of string data |
| Read a list | &Vec<T> | &[T] | Works with vectors, arrays, and slices |
| Read a value | &Box<T> | &T | Does not care how the value is stored |
&String vs &str
| Type |
|---|
Cheat Sheet
Quick rules
- Prefer
&strover&String - Prefer
&[T]over&Vec<T> - Prefer
&Tover&Box<T> - Use owned types only when ownership is required
- Use the least specific type your function needs
Common patterns
fn read_text(s: &str) {}
fn read_items(items: &[i32]) {}
fn read_value(x: &i32) {}
When to use owned types
fn takes_ownership(name: String) {}
fn consumes_vector(values: Vec<i32>) {}
fn stores_boxed_value(value: Box<i32>) {}
Why slices and borrowed views help
FAQ
Why is &str preferred over &String in Rust?
Because &str is more general. It can represent borrowed string data from string literals, String, and string slices.
Is &Vec<T> ever wrong?
Not always. It is fine if you truly need vector-specific behavior such as capacity(). But for read-only access to elements, &[T] is usually better.
Why is &Box<T> usually unnecessary?
Because most functions only care about the value T, not that it is stored in a Box. A parameter of type &T is usually enough.
Does using &str or &[T] improve performance?
The main benefit is API flexibility and clarity. It also avoids unnecessary constraints, and borrowed views are efficient.
Can I still pass a String to a function that takes &str?
Yes. Rust can coerce &String to in many common cases.
Mini Project
Description
Build a small set of utility functions for a command-line shopping and greeting program. The project demonstrates how to design flexible Rust function signatures using &str, slices, and plain references instead of &String, &Vec<T>, and &Box<T>. This is useful because real Rust code often needs helper functions that can work with many kinds of inputs without forcing callers into one specific container type.
Goal
Create utility functions that accept borrowed views of data so they can work with string literals, String, arrays, vectors, and boxed values.
Requirements
- Write a greeting function that accepts any readable string input.
- Write a function that sums a list of prices using a slice.
- Write a function that checks whether a number is even using a plain reference.
- Call the functions with different input types such as
String, string literals, arrays, vectors, andBox<i32>. - Print the results to confirm the functions work correctly.
Keep learning
Related questions
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Default Function Arguments in Rust: What to Use Instead
Learn how Rust handles default function arguments, why they are not supported, and practical patterns to achieve similar behavior.
Fixing Rust "linker 'cc' not found" on Debian in WSL
Learn why Rust shows "linker 'cc' not found" on Debian in WSL and how to fix it by installing the required C build tools.