Por que adicionar um segundo impl impede uma menor coerção do argumento?

10

Me deparei com esse problema ao tentar adicionar o impl Add<char> for Stringà biblioteca padrão. Mas podemos replicá-lo facilmente, sem travessuras de operador. Começamos com isso:

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
    }
}

Simples o suficiente. Com isso, o seguinte código compila:

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

Note-se que, neste caso, a segunda expressão argumento ( &b) tem o tipo &String. Ele é então coagido &stre a chamada de função funciona.

No entanto , vamos tentar adicionar o seguinte impl:

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

( Tudo no Playground )

Agora a MyAdd::add(a, &b)expressão acima leva ao seguinte erro:

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>>

Por que é que? Para mim, parece que a deref-coerção é feita apenas quando há apenas um candidato a uma função. Mas isso parece errado para mim. Por que as regras seriam assim? Tentei examinar a especificação, mas não encontrei nada sobre argumento sob coerção.

Lukas Kalbertodt
fonte
Isso me lembra essa resposta (escrevi). O compilador conhece a característica genericamente e, quando há apenas uma implque se aplica, pode desambiguar escolhendo o argumento de tipo usado nela impl. Nas outras perguntas e respostas, usei essa capacidade de fazer o compilador (ao que parece) escolher um implno local da chamada, o que geralmente não é possível. Presumivelmente, nesse caso, é o que permite que ele faça uma coerção menor. Mas isso é apenas um palpite.
trentcl
2
Aqui está um comentário que afirma que, se apenas um impl é encontrado, o compilador "o confirma" ansiosamente, o que permite que outras coações (entre outras coisas) aconteçam. Isso não acontece para vários candidatos a impl. Acho que essa é a resposta, mas ainda gostaria de saber mais. Este capítulo do livro rustc pode ajudar, mas, tanto quanto posso dizer, não diz nada especificamente sobre isso.
Lukas Kalbertodt 17/11/19

Respostas:

0

Como você mesmo explicou, o compilador trata o caso em que há apenas um válido implespecialmente e pode usar isso para conduzir a inferência de tipo:

Aqui está um comentário que afirma que, se apenas um impl é encontrado, o compilador "o confirma" avidamente, o que permite que outras coações (entre outras coisas) aconteçam. Isso não acontece para vários candidatos a impl.

A segunda parte é que a deref coerção ocorrerá apenas em locais onde o tipo esperado é conhecido, não ocorre especulativamente. Veja sites de coerção na referência. A seleção do implemento e a inferência de tipo devem primeiro encontrar explicitamente o que MyAdd::add(&str)seria esperado, para tentar coagir o argumento &str.

Se uma solução alternativa for necessária nessa situação, use uma expressão como &*bou &b[..]ou b.as_str()para o segundo argumento.

ramslök
fonte