J'ai écrit du code Rust qui prend un &String
comme argument:
fn awesome_greeting(name: &String) {
println!("Wow, you are awesome, {}!", name);
}
J'ai également écrit du code qui prend une référence à un Vec
ou Box
:
fn total_price(prices: &Vec<i32>) -> i32 {
prices.iter().sum()
}
fn is_even(value: &Box<i32>) -> bool {
**value % 2 == 0
}
Cependant, j'ai reçu des commentaires selon lesquels le faire comme ça n'est pas une bonne idée. Pourquoi pas?
TL; DR: On peut utiliser à la place &str
, &[T]
ou &T
pour permettre un code plus générique.
L'une des principales raisons d'utiliser un String
ou un Vec
est qu'ils permettent d'augmenter ou de diminuer la capacité. Cependant, lorsque vous acceptez une référence immuable, vous ne pouvez utiliser aucune de ces méthodes intéressantes sur Vec
ou String
.
Accepter un &String
, &Vec
ou &Box
aussi nécessite l'argument à allouer sur le tas avant d'appeler la fonction. Accepter un &str
autorise un littéral de chaîne (enregistré dans les données du programme) et accepte un &[T]
ou &T
autorise un tableau ou une variable allouée à la pile. Une allocation inutile est une perte de performance. Cela est généralement exposé immédiatement lorsque vous essayez d'appeler ces méthodes dans un test ou une méthode main
:
awesome_greeting(&String::from("Anna"));
total_price(&vec![42, 13, 1337])
is_even(&Box::new(42))
Une autre considération de performance est que &String
, &Vec
et &Box
introduire une couche d'indirection inutile car vous devez déréférencer le &String
pour obtenir un String
, puis effectuez un deuxième déréférencement pour finir à &str
.
Au lieu de cela, vous devez accepter une tranche de chaîne (&str
), une tranche (&[T]
), ou simplement une référence (&T
). UNE &String
, &Vec<T>
ou &Box<T>
sera automatiquement contraint à un &str
, &[T]
ou &T
, respectivement.
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
}
Vous pouvez maintenant appeler ces méthodes avec un ensemble de types plus large. Par exemple, awesome_greeting
peut être appelé avec un littéral de chaîne ("Anna"
) ou un String
alloué. total_price
peut être appelé avec une référence à un tableau (&[1, 2, 3]
) ou un Vec
alloué.
Si vous souhaitez ajouter ou supprimer des éléments de String
ou Vec<T>
, vous pouvez prendre une référence mutable (&mut String
ou &mut Vec<T>
):
fn add_greeting_target(greeting: &mut String) {
greeting.Push_str("world!");
}
fn add_candy_prices(prices: &mut Vec<i32>) {
prices.Push(5);
prices.Push(25);
}
Spécifiquement pour les tranches, vous pouvez également accepter un &mut [T]
ou &mut str
. Cela vous permet de muter une valeur spécifique à l'intérieur de la tranche, mais vous ne pouvez pas modifier le nombre d'éléments à l'intérieur de la tranche (ce qui signifie qu'il est très restreint pour les chaînes):
fn reset_first_price(prices: &mut [i32]) {
prices[0] = 0;
}
fn lowercase_first_ascii_character(s: &mut str) {
if let Some(f) = s.get_mut(0..1) {
f.make_ascii_lowercase();
}
}
En plus de réponse de Shepmaster , une autre raison d'accepter un &str
(et de même &[T]
etc) est dû à tous les autres types en plus String
et &str
qui satisfont également Deref<Target = str>
. L'un des exemples les plus notables est Cow<str>
, ce qui vous permet d'être très flexible quant à savoir si vous traitez des données détenues ou empruntées.
Si tu as:
fn awesome_greeting(name: &String) {
println!("Wow, you are awesome, {}!", name);
}
Mais vous devez l'appeler avec un Cow<str>
, vous devrez faire ceci:
let c: Cow<str> = Cow::from("hello");
// Allocate an owned String from a str reference and then makes a reference to it anyway!
awesome_greeting(&c.to_string());
Lorsque vous changez le type d'argument en &str
, vous pouvez utiliser Cow
de façon transparente, sans allocation inutile, tout comme avec String
:
let c: Cow<str> = Cow::from("hello");
// Just pass the same reference along
awesome_greeting(&c);
let c: Cow<str> = Cow::from(String::from("hello"));
// Pass a reference to the owned string that you already have
awesome_greeting(&c);
Accepter &str
rend l'appel de votre fonction plus uniforme et plus pratique, et la méthode la plus "simple" est désormais la plus efficace. Ces exemples fonctionneront également avec Cow<[T]>
etc.