Чому додавання другого імпульту запобігає примусовому застосуванню аргументу?


10

Я зіткнувся з цим питанням, намагаючись додати імпл 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
Ось коментар, в якому йдеться про те, що якщо буде знайдено лише один імпл, компілятор "охоче підтверджує" це, що дозволяє виконувати корекціони дереф (серед іншого). Цього не відбувається для декількох кандидатів. Тож я гадаю, що така відповідь, але мені б хотілося знати більше. Цей розділ у руст-книзі може допомогти, але, наскільки я можу сказати, він конкретно не говорить про це.
Лукас Кальберттт

Відповіді:


0

Як ви самі пояснили, компілятор розглядає випадок, коли існує лише одна дійсна implспеціально, і може використовувати це для виведення типу висновку:

Ось коментар, в якому йдеться про те, що якщо буде знайдено лише один імпл, компілятор "охоче підтверджує" це, що дозволяє виконувати корекціони дереф (серед іншого). Цього не відбувається для декількох кандидатів.

Друга частина полягає в тому, що примус до деревини відбуватиметься лише на місцях, де відомий очікуваний тип, це не відбувається спекулятивно. Дивіться сайти примусу в посиланні. Вибір імпл і умовивід слід спочатку чітко знайти те, MyAdd::add(&str)що очікувалося, щоб спробувати примусити аргумент до &str.

Якщо в цій ситуації потрібне вирішення, використовуйте вираз, як &*bабо &b[..]або b.as_str()для другого аргументу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.