web-dev-qa-db-fra.com

Comment puis-je implémenter le trait Add pour une référence à une structure?

J'ai créé une structure à deux éléments Vector et je veux surcharger l'opérateur +.

J'ai fait en sorte que toutes mes fonctions et méthodes prennent des références, plutôt que des valeurs, et je veux que l'opérateur + Fonctionne de la même manière.

impl Add for Vector {
    fn add(&self, other: &Vector) -> Vector {
        Vector {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

Selon la variante que j'essaie, j'obtiens soit des problèmes de durée de vie, soit des incompatibilités de type. Plus précisément, l'argument &self Ne semble pas être traité comme le bon type.

J'ai vu des exemples avec des arguments de modèle sur impl ainsi que Add, mais ils entraînent simplement des erreurs différentes.

J'ai trouvé Comment un opérateur peut-il être surchargé pour différents types RHS et valeurs de retour? mais le code dans la réponse ne fonctionne pas même si je mets un use std::ops::Mul; En haut.

J'utilise rustc 1.0.0 tous les soirs (ed530d7a3 2015-01-16 22:41:16 +0000)

Je n'accepterai pas "vous n'avez que deux champs, pourquoi utiliser une référence" comme réponse; Et si je voulais une structure à 100 éléments? J'accepterai une réponse qui démontre que même avec une grande structure, je devrais passer par valeur, si tel est le cas (je ne pense pas, cependant.) Je suis intéressé à connaître une bonne règle de base pour la taille de la structure et en passant par valeur vs struct, mais ce n'est pas la question actuelle.

49
Jeremy Sorensen

Vous devez implémenter Add sur &Vector plutôt que sur Vector.

impl<'a, 'b> Add<&'b Vector> for &'a Vector {
    type Output = Vector;

    fn add(self, other: &'b Vector) -> Vector {
        Vector {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

Dans sa définition, Add::add prend toujours self par valeur. Mais les références sont des types comme les autres1, afin qu'ils puissent également mettre en œuvre des traits. Lorsqu'un trait est implémenté sur un type de référence, le type de self est une référence; la référence est passée par valeur. Normalement, le passage par valeur dans Rust implique le transfert de propriété, mais lorsque les références sont passées par valeur, elles sont simplement copiées (ou réorientées/déplacées s'il s'agit d'une référence mutable), et cela ne signifie pas transférer la propriété du référent (car une référence ne possède pas son référent en premier lieu). Compte tenu de tout cela, il est logique pour Add::add (et de nombreux autres opérateurs) pour prendre self par valeur: si vous devez vous approprier les opérandes, vous pouvez implémenter Add sur les structures/énumérations directement, et si vous ne le faites pas , vous pouvez implémenter Add sur les références.

Ici, self est de type &'a Vector, car c'est le type sur lequel nous implémentons Add.

Notez que j'ai également spécifié le paramètre de type RHS avec une durée de vie différente pour souligner le fait que les durées de vie des deux paramètres d'entrée ne sont pas liées.


1 En fait, les types de référence sont spéciaux en ce sens que vous pouvez implémenter des traits pour les références aux types définis dans votre caisse (c'est-à-dire si vous êtes autorisé à implémenter un trait pour T, vous êtes également autorisé à l'implémenter pour &T). &mut T et Box<T> ont le même comportement, mais ce n'est pas vrai en général pour U<T>U n'est pas défini dans la même caisse.

48
Francis Gagné

Si vous souhaitez prendre en charge tous les scénarios, vous devez prendre en charge toutes les combinaisons:

  • & T op U
  • T op & U
  • & T op & U
  • T op U

In Rust proper, cela a été fait via une macro interne .

Heureusement, il y a une Rust caisse, impl_os , qui propose également une macro pour écrire ce passe-partout pour nous: la caisse propose la impl_op_ex! = macro, qui génère toutes les combinaisons.

Voici leur échantillon:

#[macro_use] extern crate impl_ops;
use std::ops;

impl_op_ex!(+ |a: &DonkeyKong, b: &DonkeyKong| -> i32 { a.bananas + b.bananas });

fn main() {
    let total_bananas = &DonkeyKong::new(2) + &DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
    let total_bananas = &DonkeyKong::new(2) + DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
    let total_bananas = DonkeyKong::new(2) + &DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
    let total_bananas = DonkeyKong::new(2) + DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
}

Encore mieux, ils ont un impl_op_ex_commutative! qui générera également les opérateurs avec les paramètres inversés si votre opérateur est commutatif.

3
Emmanuel Touzery