Pourquoi l'ajout d'un deuxième implément empêche-t-il une contrainte excessive de l'argument?

10

J'ai rencontré ce problème en essayant d'ajouter l'impl Add<char> for Stringà la bibliothèque standard. Mais nous pouvons le reproduire facilement, sans manigances d'opérateur. Nous commençons par ceci:

trait MyAdd<Rhs> {
    fn add(self, rhs: Rhs) -> Self;
}

impl MyAdd<&str> for String {
    fn add(mut self, rhs: &str) -> Self {
        self.push_str(rhs);
        self
    }
}

Assez simple. Avec cela, le code suivant se compile:

let a = String::from("a");
let b = String::from("b");
MyAdd::add(a, &b);

Notez que dans ce cas, la seconde expression d'argument ( &b) a le type &String. Il est ensuite dé-contraint &stret l'appel de fonction fonctionne.

Cependant , essayons d'ajouter l'impl suivant:

impl MyAdd<char> for String {
    fn add(mut self, rhs: char) -> Self {
        self.push(rhs);
        self
    }
}

( Tout sur Playground )

Maintenant, l' MyAdd::add(a, &b)expression ci-dessus conduit à l'erreur suivante:

error[E0277]: the trait bound `std::string::String: MyAdd<&std::string::String>` is not satisfied
  --> src/main.rs:24:5
   |
2  |     fn add(self, rhs: Rhs) -> Self;
   |     ------------------------------- required by `MyAdd::add`
...
24 |     MyAdd::add(a, &b);
   |     ^^^^^^^^^^ the trait `MyAdd<&std::string::String>` is not implemented for `std::string::String`
   |
   = help: the following implementations were found:
             <std::string::String as MyAdd<&str>>
             <std::string::String as MyAdd<char>>

Pourquoi donc? Il me semble que la deref-coercition ne se fait que lorsqu'il n'y a qu'une seule fonction candidate. Mais cela me semble faux. Pourquoi les règles seraient-elles ainsi? J'ai essayé de parcourir la spécification, mais je n'ai rien trouvé sur l'argument deref coercion.

Lukas Kalbertodt
la source
Cela me rappelle cette réponse (j'ai écrit). Le compilateur connaît le trait de manière générique, et lorsqu'il n'y en a qu'un implqui s'applique, il peut lever l'ambiguïté en choisissant l'argument type utilisé dans cela impl. Dans les autres questions et réponses, j'ai utilisé cette capacité pour que le compilateur (apparemment) choisisse un implsur le site d'appel, ce qui n'est généralement pas possible. Vraisemblablement dans ce cas, c'est ce qui lui permet d'exercer une contrainte deref. Mais ce n'est qu'une supposition.
trentcl
2
Voici un commentaire qui déclare que si un seul implément est trouvé, le compilateur le "confirme avec impatience", ce qui permet aux contraintes deref (entre autres) de se produire. Cela ne se produit pas pour plusieurs candidats impl. Donc je suppose que c'est un peu la réponse, mais j'aimerais toujours en savoir plus. Ce chapitre du livre rustc pourrait aider, mais pour autant que je sache, il ne dit pas spécifiquement quelque chose à ce sujet.
Lukas Kalbertodt

Réponses:

0

Comme vous l'avez expliqué vous-même, le compilateur traite le cas où il n'y en a qu'un seul implspécialement valide , et peut l'utiliser pour conduire l'inférence de type:

Voici un commentaire qui déclare que si un seul implément est trouvé, le compilateur le "confirme avec impatience", ce qui permet aux contraintes deref (entre autres) de se produire. Cela ne se produit pas pour plusieurs candidats impl.

La deuxième partie est que la coercition deref ne se produira que sur les sites où le type attendu est connu, cela ne se produit pas de manière spéculative. Voir sites de coercition dans la référence. La sélection d'impl et l'inférence de type doivent d' abord trouver explicitement ce qui MyAdd::add(&str)serait attendu, pour tenter de contraindre l'argument à &str.

Si une solution de contournement est nécessaire dans cette situation, utilisez une expression comme &*bou &b[..]ou b.as_str()pour le deuxième argument.

ramslök
la source