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.