Ceci est maintenant abordé dans la deuxième édition de The Rust Programming Language . Cependant, plongeons un peu en plus.
Commençons par un exemple plus simple.
Quand est-il approprié d'utiliser une méthode de trait?
Il existe plusieurs façons de fournir une liaison tardive :
trait MyTrait {
fn hello_word(&self) -> String;
}
Ou:
struct MyTrait<T> {
t: T,
hello_world: fn(&T) -> String,
}
impl<T> MyTrait<T> {
fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait<T>;
fn hello_world(&self) -> String {
(self.hello_world)(self.t)
}
}
Indépendamment de toute stratégie de mise en œuvre / performance, les deux extraits ci-dessus permettent à l'utilisateur de spécifier de manière dynamique comment hello_world
doit se comporter.
La seule différence (sémantiquement) est que l' trait
implémentation garantit que pour un type donné T
implémentant le trait
, hello_world
aura toujours le même comportement alors que lestruct
implémentation permet d'avoir un comportement différent sur une base par instance.
Que l'utilisation d'une méthode soit appropriée ou non dépend du cas d'utilisation!
Quand est-il approprié d'utiliser un type associé?
De la même manière que les trait
méthodes ci-dessus, un type associé est une forme de liaison tardive (bien qu'elle se produise lors de la compilation), permettant à l'utilisateur de trait
spécifier pour une instance donnée le type à remplacer. Ce n'est pas le seul moyen (d'où la question):
trait MyTrait {
type Return;
fn hello_world(&self) -> Self::Return;
}
Ou:
trait MyTrait<Return> {
fn hello_world(&Self) -> Return;
}
Sont équivalents à la liaison tardive des méthodes ci-dessus:
- le premier impose que pour un donné
Self
il y ait un seulReturn
associé
- le second, au contraire, permet de mettre en œuvre
MyTrait
pour Self
de multiplesReturn
La forme la plus appropriée dépend de la pertinence d'appliquer l'unicité ou non. Par exemple:
Deref
utilise un type associé car sans unicité, le compilateur deviendrait fou lors de l'inférence
Add
utilise un type associé car son auteur pensait que compte tenu des deux arguments, il y aurait un type de retour logique
Comme vous pouvez le voir, s'il Deref
s'agit d'un cas d'utilisation évident (contrainte technique), le cas de Add
est moins clair: peut-être serait-il logique i32 + i32
de céder l'un i32
ou l' autre ou Complex<i32>
selon le contexte? Néanmoins, l’auteur a exercé son jugement et a décidé qu’il n’était pas nécessaire de surcharger le type de retour pour les ajouts.
Ma position personnelle est qu'il n'y a pas de bonne réponse. Néanmoins, au-delà de l'argument d'unicité, je mentionnerais que les types associés facilitent l'utilisation du trait car ils diminuent le nombre de paramètres à spécifier, donc au cas où les avantages de la flexibilité de l'utilisation d'un paramètre de trait régulier ne sont pas évidents, je suggérez de commencer par un type associé.
trait/struct MyTrait/MyStruct
autorise exactement unimpl MyTrait for
ouimpl MyStruct
.trait MyTrait<Return>
autorise plusieursimpl
s car il est générique.Return
peut être de n'importe quel type. Les structures génériques sont les mêmes.Les types associés sont un mécanisme de regroupement , ils doivent donc être utilisés lorsqu'il est judicieux de regrouper des types.
Le
Graph
trait introduit dans la documentation en est un exemple. Vous voulez que unGraph
soit générique, mais une fois que vous avez un type spécifique deGraph
, vous ne voulez plus que les typesNode
ouEdge
varient plus. Un particulierGraph
ne voudra pas varier ces types au sein d'une même implémentation, et en fait, veut qu'ils soient toujours les mêmes. Ils sont regroupés, ou on pourrait même dire associés .la source