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