Natknąłem się na ten problem, próbując dodać impl Add<char> for String
do 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 &str
i 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
}
}
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.
źródło
impl
która ma zastosowanie, może jednoznacznie wybierać użyty w niej argument typuimpl
. W innych pytaniach i odpowiedziach wykorzystałem tę zdolność, aby kompilator (jak się wydaje) wybrałimpl
stronę 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.Odpowiedzi:
Jak sam wyjaśniłeś, kompilator traktuje przypadek, w którym jest tylko jeden ważny
impl
specjalnie, i może użyć tego do kierowania wnioskowania typu: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
&*b
lub&b[..]
czyb.as_str()
dla drugiego argumentu.źródło