262 votes

Pourquoi est-il déconseillé d'accepter une référence à un String (&String), Vec (&Vec) ou Box (&Box) comme argument de fonction ?

J'ai écrit un 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 fichier Vec o 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 cette façon de procéder n'est pas une bonne idée. Pourquoi pas ?

338voto

Shepmaster Points 1732

TL;DR : On peut à la place utiliser &str , &[T] o &T pour permettre un code plus générique.


  1. L'une des principales raisons d'utiliser un String ou un Vec c'est parce 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 le fichier Vec o String .

  2. Accepter un &String , &Vec o &Box également nécessite l'argument doit être alloué sur le tas avant que vous puissiez appeler la fonction. Accepter un &str permet un littéral de chaîne de caractères (sauvegardé dans les données du programme) et accepte un &[T] o &T permet un tableau ou une variable allouée à la pile. Une allocation inutile entraîne une perte de performance. Ce problème est généralement mis en évidence dès que vous essayez d'appeler ces méthodes dans un test ou dans un fichier main méthode :

    awesome_greeting(&String::from("Anna"));
    
    total_price(&vec![42, 13, 1337])
    
    is_even(&Box::new(42))
  3. Une autre considération de performance est que &String , &Vec y &Box introduisent une couche inutile d'indirection, car vous devez déréférencer l'adresse de l'utilisateur. &String pour obtenir un String puis effectuer une deuxième déréférence pour aboutir à &str .

Au lieu de cela, vous devez accepter un tranche de chaîne ( &str ), a tranche ( &[T] ), ou juste une référence ( &T ). A &String , &Vec<T> o &Box<T> sera automatiquement contraint (via deref coercition ) à un &str , &[T] o &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 désormais appeler ces méthodes avec un ensemble plus large de types. Par exemple, awesome_greeting peut être appelé avec une chaîne littérale ( "Anna" ) ou une allocation String . total_price peut être appelé avec une référence à un tableau ( &[1, 2, 3] ) ou une allocation Vec .


Si vous souhaitez ajouter ou supprimer des éléments de la liste des String o Vec<T> vous pouvez prendre un référence mutable ( &mut String o &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 une &mut [T] o &mut str . Cela vous permet de modifier 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 que c'est très limité pour les chaînes de caractères) :

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();
    }
}

16 votes

Que diriez-vous d'un tl;dr au début ? Cette réponse est déjà un peu longue. Quelque chose comme " &str est plus général (c'est-à-dire qu'il impose moins de restrictions) sans réduire les capacités" ? Par ailleurs, le point 3 n'est souvent pas si important que cela, je pense. En général, Vec et String vivront sur la pile et souvent même quelque part près du cadre de la pile actuelle. La pile est généralement chaude et la déréférence sera servie à partir d'un cache du CPU.

5 votes

@Shepmaster : En ce qui concerne le coût d'allocation, il pourrait être utile de mentionner la question particulière des sous-chaînes/tranches lorsqu'on parle d'allocation obligatoire. total_price(&prices[0..4]) ne nécessite pas l'allocation d'un nouveau vecteur pour la tranche.

10 votes

C'est une excellente réponse. Je viens juste de commencer à utiliser Rust et j'avais du mal à savoir quand je devais utiliser un fichier de type &str y por qué (je viens de Python, donc je n'ai pas l'habitude de traiter explicitement les types). Tout cela est parfaitement clair

58voto

Peter Hall Points 2991

En plus de La réponse de Shepmaster Une raison supplémentaire d'accepter un &str (et de la même façon &[T] etc.) c'est à cause de tous les autres types en plus de String y &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 souple quant à savoir si vous traitez des données propres ou empruntées.

Si vous avez :

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

Mais vous devez l'appeler avec un Cow<str> vous devrez le faire :

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 les 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 manière la plus "facile" est maintenant aussi la plus efficace. Ces exemples fonctionneront également avec Cow<[T]> etc.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X