¿Por qué agregar un segundo impl evita la deserción de la coerción del argumento?

10

Me encontré con este problema al intentar agregar el impl Add<char> for Stringa la biblioteca estándar. Pero podemos replicarlo fácilmente, sin travesuras del operador. Comenzamos con esto:

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

Suficientemente simple. Con esto, se compila el siguiente código:

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

Tenga en cuenta que en este caso, la segunda expresión de argumento ( &b) tiene el tipo &String. Luego se deserige en &stry la llamada a la función funciona.

Sin embargo , intentemos agregar el siguiente impl:

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

( Todo en el patio de recreo )

Ahora la MyAdd::add(a, &b)expresión anterior lleva al siguiente error:

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

¿Porqué es eso? Para mí, parece que la deserción-coerción solo se realiza cuando solo hay una función candidata. Pero esto me parece mal. ¿Por qué las reglas serían así? Traté de mirar a través de la especificación, pero no he encontrado nada sobre argumentos de deserción.

Lukas Kalbertodt
fuente
Esto me recuerda esta respuesta (escribí). El compilador conoce el rasgo genéricamente, y cuando solo hay uno implque aplica, puede desambiguar eligiendo el argumento de tipo utilizado en eso impl. En las otras preguntas y respuestas, utilicé esta capacidad para hacer que el compilador (parezca) elegir una implen el sitio de la llamada, que es algo que generalmente no puede hacer. Presumiblemente, en este caso, eso es lo que le permite hacer una coacción deref. Pero eso es solo una suposición.
trentcl
2
Aquí hay un comentario que establece que si solo se encuentra un impl, el compilador lo "confirma con entusiasmo", lo que permite que se produzcan coerciones deref (entre otras cosas). Eso no sucede para múltiples candidatos implícitos. Así que supongo que esa es la respuesta, pero me encantaría saber más. Este capítulo en el libro rustc podría ayudar, pero por lo que puedo decir, no dice específicamente algo sobre esto.
Lukas Kalbertodt

Respuestas:

0

Como usted mismo explicó, el compilador trata el caso en el que solo hay uno válido implespecialmente, y puede usar esto para conducir la inferencia de tipos:

Aquí hay un comentario que establece que si solo se encuentra un impl, el compilador lo "confirma con entusiasmo", lo que permite que se produzcan coerciones deref (entre otras cosas). Eso no sucede para múltiples candidatos implícitos.

La segunda parte es que la coerción de deref solo ocurrirá en sitios donde se conoce el tipo esperado, no ocurre especulativamente. Ver sitios de coerción en la referencia. La selección implícita y la inferencia de tipos primero deben encontrar explícitamente lo que MyAdd::add(&str)se esperaría, para intentar forzar el argumento a&str .

Si se necesita una solución en esta situación, utilice una expresión como &*bo &b[..]o b.as_str()para el segundo argumento.

ramslök
fuente