Почему добавление второго impl предотвращает принудительное приведение аргумента?

10

Я сталкивался с этой проблемой при попытке добавить impl Add<char> for Stringв стандартную библиотеку. Но мы можем повторить это легко, без махинаций оператора. Начнем с этого:

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

Достаточно просто. С этим компилируется следующий код:

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

Обратите внимание, что в этом случае выражение второго аргумента ( &b) имеет тип &String. Затем он принудительно вызывается, &strи вызов функции работает.

Тем не менее , давайте попробуем добавить следующее значение:

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

( Все на детской площадке )

Теперь приведенное MyAdd::add(a, &b)выше выражение приводит к следующей ошибке:

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

Это почему? Мне кажется, что дереф-принуждение выполняется только тогда, когда есть только один кандидат на функцию. Но это мне кажется неправильным. Почему правила таковы? Я пытался просмотреть спецификацию, но я не нашел ничего по поводу доводов deref.

Лукас Калбертодт
источник
Это напоминает мне об этом ответе (я написал). Компилятор знает черту в общем, и, когда implприменяется только один , он может устранить неоднозначность, выбрав аргумент типа, используемый в этом impl. В других вопросах и ответах я использовал эту возможность, чтобы компилятор (кажется) выбирал объект implна сайте вызова, что обычно невозможно. Предположительно, в этом случае это то, что позволяет делать разовое принуждение. Но это только предположение.
trentcl
2
Вот комментарий, в котором говорится, что, если найден только один impl, компилятор «охотно подтверждает» его, что позволяет осуществлять разыменование (среди прочего). Это не происходит для нескольких кандидатов. Так что я думаю, что это своего рода ответ, но я бы хотел узнать больше. Эта глава в книге о ржавчине может помочь, но, насколько я могу судить, она ничего конкретно не говорит об этом.
Лукас Калбертодт

Ответы:

0

Как вы сами объяснили, компилятор обрабатывает случай, когда существует только один допустимый impl, и может использовать это для вывода типа диска:

Вот комментарий, в котором говорится, что, если найден только один impl, компилятор «охотно подтверждает» его, что позволяет осуществлять разыменование (среди прочего). Это не происходит для нескольких кандидатов.

Вторая часть заключается в том, что принудительное приведение в действие будет происходить только в тех местах, где известен ожидаемый тип, а не спекулятивно. Смотрите сайты принуждения в ссылке. Выбор Impl и вывод типа должны сначала явно найти то, MyAdd::add(&str)что ожидалось, чтобы попытаться привести аргумент к &str.

Если в этой ситуации требуется обходной путь, используйте выражение наподобие &*bили &b[..]или b.as_str()для второго аргумента.

ramslök
источник