5 votes

Opposé du trait Borrow pour les types Copy?

J'ai vu le trait Borrow utilisé pour définir des fonctions qui acceptent soit un type propriétaire, soit une référence, par exemple T ou &T. La méthode borrow() est ensuite appelée dans la fonction pour obtenir &T.

Est-ce qu'il existe un trait qui permet l'inverse (c'est-à-dire une fonction qui accepte T ou &T et obtient T) pour les types Copy ?

Par exemple pour cet exemple :

use std::borrow::Borrow;

fn foo>(value: T) -> u32 {
    *value.borrow()
}

fn main() {
    println!("{}", foo(&5));
    println!("{}", foo(5));
}

Cela appelle borrow() pour obtenir une référence, qui est ensuite immédiatement déréférencée.

Existe-t-il une autre implémentation qui copie simplement la valeur si T est passé en argument, et déréférence si &T a été donné ? Ou est-ce la façon idiomatique d'écrire ce genre de chose ?

5voto

trentcl Points 371

Il n'y a pas vraiment de trait inverse pour Borrow, car ce n'est pas vraiment utile comme limite sur les fonctions de la même manière que Borrow l'est. La raison a à voir avec la possession.

Pourquoi "inverse Borrow" est-il moins utile que Borrow ?

Fonctions qui nécessitent des références

Considérez une fonction qui a seulement besoin de référencer son argument :

fn puts(arg: &str) {
    println!("{}", arg);
}

Accepter String serait absurde ici, car puts n'a pas besoin de prendre possession des données, mais accepter &str signifie que nous pourrions parfois obliger l'appelant à conserver les données plus longtemps que nécessaire :

{
    let output = create_some_string();
    output.push_str(some_other_string);
    puts(&output);
    // faire autre chose mais ne jamais utiliser `output` à nouveau
} // `output` n'est pas libéré jusqu'ici

Le problème est que output n'est plus nécessaire après avoir été passé à puts, et l'appelant le sait, mais puts nécessite une référence, donc output doit rester en vie jusqu'à la fin du bloc. De toute évidence, vous pouvez toujours corriger cela dans l'appelant en ajoutant plus de blocs et parfois un let, mais puts peut aussi être rendu générique pour permettre à l'appelant de déléguer la responsabilité de nettoyer output :

fn puts>(arg: T) {
    println!("{}", arg.borrow());
}

Accepter T: Borrow pour puts donne à l'appelant la flexibilité de décider s'il veut conserver l'argument ou le déplacer dans la fonction.

Fonctions qui ont besoin de valeurs possédées

Maintenant, envisagez le cas d'une fonction qui a effectivement besoin de prendre possession :

struct Wrapper(String);
fn wrap(arg: String) -> Wrapper {
    Wrapper(arg)
}

Dans ce cas, accepter &str serait absurde, car wrap devrait appeler to_owned() dessus. Si l'appelant a une String qu'il n'utilise plus, cela copierait inutilement les données qui auraient pu simplement être déplacées dans la fonction. Dans ce cas, accepter String est l'option la plus flexible, car elle permet à l'appelant de décider s'il doit faire un clone ou passer une String existante. Avoir un trait "inverse Borrow" n'ajouterait pas de flexibilité que arg: String ne fournit pas déjà.

Mais String n'est pas toujours l'argument le plus ergonomique, car il existe plusieurs types différents de chaînes : &str, Cow, Box... Nous pouvons rendre wrap un peu plus ergonomique en disant qu'il accepte n'importe quoi qui peut être converti en une String.

fn wrap>(arg: T) -> Wrapper {
    Wrapper(arg.into())
}

Cela signifie que vous pouvez l'appeler comme wrap("hello, world") sans avoir à appeler .to_owned() sur le littéral. Ce n'est pas vraiment un avantage de flexibilité -- l'appelant peut toujours appeler .into() à la place sans perte de généralité -- mais c'est un avantage ergonomique.

Que dire des types Copy ?

Maintenant, vous avez demandé à propos des types Copy. Pour la plupart, les arguments ci-dessus s'appliquent toujours. Si vous écrivez une fonction qui, comme puts, a seulement besoin d'un &A, utiliser T: Borrow pourrait être plus flexible pour l'appelant ; pour une fonction comme wrap qui a besoin de toute A, il est plus flexible d'accepter simplement A. Mais pour les types Copy, l'avantage ergonomique d'accepter T: Into est beaucoup moins clair.

  • Pour les types entiers, parce que les génériques perturbent l'inférence de type, les utiliser rend généralement l'utilisation des littéraux moins ergonomique ; vous pourriez finir par devoir annoter explicitement les types.
  • Étant donné que &u32 n'implémente pas Into, ce tour particulier ne fonctionnerait de toute façon pas ici.
  • Étant donné que les types Copy sont facilement disponibles en tant que valeurs possédées, il est moins courant de les utiliser par référence en premier lieu.
  • Enfin, transformer un &A en un A quand A: Copy est aussi simple que d'ajouter simplement * ; pouvoir sauter cette étape n'est probablement pas un avantage suffisant pour compenser la complexité accrue de l'utilisation de génériques dans la plupart des cas.

En conclusion, foo devrait presque certainement accepter simplement valeur: u32 et laisser l'appelant décider comment obtenir cette valeur.

À voir aussi


¹ Pour cette fonction particulière, vous voudriez probablement AsRef, car vous ne vous basez pas sur les garanties supplémentaires de Borrow, et le fait que tous les T implémentent Borrow n'est généralement pas pertinent pour les types unsized tels que str. Mais cela est hors sujet.

2voto

Sunreef Points 2654

Avec la fonction que vous avez, vous ne pouvez utiliser qu'un u32 ou un type qui peut être emprunté en tant que u32.

Vous pouvez rendre votre fonction plus générique en utilisant un second argument de modèle.

fn foo>(valeur: N) -> T {
    *valeur.borrow()
}

Cependant, ceci n'est qu'une solution partielle car cela nécessitera des annotations de type dans certains cas pour fonctionner correctement.

Par exemple, cela fonctionne immédiatement avec usize:

let v = 0usize;
println!("{}", foo(v));

Ici, il n'y a aucun problème pour le compilateur à deviner que foo(v) est un usize.

Cependant, si vous essayez foo(&v), le compilateur se plaindra qu'il ne peut pas trouver le bon type de sortie T car &T pourrait implémenter plusieurs traits Borrow pour différents types. Vous devez spécifier explicitement celui que vous voulez utiliser en sortie.

let resultat: usize = foo(&v);

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