Dlaczego dodanie drugiego implu zapobiega derefowemu przymusowi argumentu?

10

Natknąłem się na ten problem, próbując dodać impl Add<char> for Stringdo standardowej biblioteki. Ale możemy to łatwo powielić, bez operatora shenanigans. Zaczynamy od tego:

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

Wystarczająco proste. Dzięki temu kompiluje się następujący kod:

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

Zauważ, że w tym przypadku drugie wyrażenie argumentu ( &b) ma typ &String. Następnie jest wymuszony na deref &stri wywołanie funkcji działa.

Jednak spróbujmy dodać następujące Impl:

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

( Wszystko na placu zabaw )

Teraz MyAdd::add(a, &b)powyższe wyrażenie prowadzi do następującego błędu:

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

Dlaczego? Wydaje mi się, że przymus derefowy jest wykonywany tylko wtedy, gdy istnieje tylko jeden kandydat na funkcję. Ale wydaje mi się to niewłaściwe. Dlaczego takie byłyby reguły? Próbowałem przejrzeć specyfikację, ale nie znalazłem nic na temat argumentu deref przymusu.

Lukas Kalbertodt
źródło
To przypomina mi tę odpowiedź (napisałem). Kompilator zna tę cechę ogólnie, a gdy jest tylko jedna, implktóra ma zastosowanie, może jednoznacznie wybierać użyty w niej argument typu impl. W innych pytaniach i odpowiedziach wykorzystałem tę zdolność, aby kompilator (jak się wydaje) wybrał implstronę wywoływania, czego zwykle nie może zrobić. Przypuszczalnie w tym przypadku to właśnie pozwala mu na pozbawienie wolności przymusu. Ale to tylko przypuszczenie.
trentcl
2
Oto komentarz, który stwierdza, że ​​jeśli zostanie znaleziony tylko jeden impl, kompilator „chętnie to potwierdzi”, co pozwala na wystąpienie derefowych koercji (między innymi). Nie dzieje się tak w przypadku wielu kandydatów impl. Więc chyba taka odpowiedź, ale nadal chciałbym wiedzieć więcej. Ten rozdział w książce Rustc może pomóc, ale o ile wiem, nie mówi o tym nic szczególnego.
Lukas Kalbertodt

Odpowiedzi:

0

Jak sam wyjaśniłeś, kompilator traktuje przypadek, w którym jest tylko jeden ważny implspecjalnie, i może użyć tego do kierowania wnioskowania typu:

Oto komentarz, który stwierdza, że ​​jeśli zostanie znaleziony tylko jeden impl, kompilator „chętnie to potwierdzi”, co pozwala na wystąpienie derefowych koercji (między innymi). Nie dzieje się tak w przypadku wielu kandydatów impl.

Druga część polega na tym, że przymus derefowy nastąpi tylko w witrynach, w których znany jest oczekiwany typ, a nie spekulacyjnie. Zobacz strony z przymusami w odnośniku. Wybór Impl i wnioskowanie o typie muszą najpierw wyraźnie znaleźć to, MyAdd::add(&str)czego można się spodziewać, aby spróbować przekonać argument &str.

Jeżeli obejście jest potrzebne w tej sytuacji użyć wyrażenia jak &*blub &b[..]czy b.as_str()dla drugiego argumentu.

ramslök
źródło