I wrote some Rust code that takes a &String
as an argument:
fn awesome_greeting(name: &String) {
println!("Wow, you are awesome, {}!", name);
}
I've also written code that takes in a reference to a Vec
or Box
:
fn total_price(prices: &Vec<i32>) -> i32 {
prices.iter().sum()
}
fn is_even(value: &Box<i32>) -> bool {
**value % 2 == 0
}
However, I received some feedback that doing it like this isn't a good idea. Why not?
TL;DR: One can instead use
&str
,&[T]
or&T
with no loss of genericity.One of the main reasons to use a
String
or aVec
is because they allow increasing or decreasing the capacity. However, when you accept an immutable reference, you cannot use any of those interesting methods on theVec
orString
.Accepting a
&String
,&Vec
or&Box
also requires an allocation before you can call the method. Unnecessary allocation is a performance loss. This is usually exposed right away when you try to call these methods in a test or amain
method:Another performance consideration is that
&String
,&Vec
and&Box
introduce an unnecessary layer of indirection as you have dereference the&String
to get aString
and then a second dereference to end up at&str
.Instead, you should accept a string slice (
&str
), a slice (&[T]
), or just a reference&T
. A&String
,&Vec<T>
or&Box<T>
will be automatically coerced to a&str
,&[T]
or&T
, respectively.Now you can call these methods with a broader set of types. For example,
awesome_greeting
can be called with a string literal ("Anna"
) or an allocatedString
.total_price
can be called with a reference to an array (&[1, 2, 3]
) or an allocatedVec
.If you'd like to add or remove items from the
String
orVec<T>
, you can take a mutable reference (&mut String
or&mut Vec<T>
):Specifically for slices, you can also accept a
&mut [T]
or&mut str
. This allows you to mutate a specific value inside the slice, but you cannot change the number of items inside the slice (which means it's very restricted for strings):In addition to Shepmaster's answer, another reason to accept a
&str
(and similarly&[T]
etc) is because of all of the other types besidesString
and&str
that also satisfyDeref<Target = str>
. One of the most notable examples isCow<str>
, which lets you be very flexible about whether you are dealing with owned or borrowed data.If you have:
But you need to call it with a
Cow<str>
, you'll have to do this:When you change the argument type to
&str
, you can useCow
seamlessly, without any unnecessary allocation, just like withString
:Accepting
&str
makes calling your function more uniform and convenient, and the "easiest" way is now also the most efficient. These examples will also work withCow<[T]>
etc.