Warum verhindert das Hinzufügen eines zweiten Impl den Deref-Zwang des Arguments?

10

Ich bin auf dieses Problem gestoßen Add<char> for String, als ich versucht habe, das Impl zur Standardbibliothek hinzuzufügen . Aber wir können es leicht replizieren, ohne Operator-Spielereien. Wir beginnen damit:

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

Einfach genug. Damit wird der folgende Code kompiliert:

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

Beachten Sie, dass in diesem Fall das zweite Argument expression ( &b) den Typ hat &String. Es wird dann deref-gezwungen &strund der Funktionsaufruf funktioniert.

Versuchen wir jedoch , das folgende Impl hinzuzufügen:

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

( Alles auf dem Spielplatz )

Der MyAdd::add(a, &b)obige Ausdruck führt nun zu folgendem Fehler:

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

Warum ist das so? Mir scheint, dass Deref-Zwang nur dann ausgeübt wird, wenn es nur einen Funktionskandidaten gibt. Aber das scheint mir falsch zu sein. Warum sollten die Regeln so sein? Ich habe versucht, die Spezifikation durchzusehen, aber ich habe nichts über Argumentationszwang gefunden.

Lukas Kalbertodt
quelle
Das erinnert mich an diese Antwort (ich schrieb). Der Compiler kennt das Merkmal generisch, und wenn nur eines implzutrifft, kann er durch Auswahl des darin verwendeten Typarguments eindeutig unterscheiden impl. In den anderen Fragen und Antworten habe ich diese Fähigkeit verwendet, um den Compiler (anscheinend) dazu zu bringen, eine implan der Aufrufstelle auszuwählen , was normalerweise nicht möglich ist. Vermutlich ist es in diesem Fall das, was es erlaubt, Zwang zu üben. Aber das ist nur eine Vermutung.
Trentcl
2
Hier ist ein Kommentar, der besagt, dass der Compiler "eifrig bestätigt", wenn nur ein Impl gefunden wird, was (unter anderem) deref Zwänge zulässt. Dies ist bei mehreren Impl-Kandidaten nicht der Fall. Ich denke, das ist die Antwort, aber ich würde immer noch gerne mehr wissen. Dieses Kapitel im Rustc-Buch mag helfen, aber soweit ich das beurteilen kann, sagt es nicht speziell etwas darüber aus.
Lukas Kalbertodt

Antworten:

0

Wie Sie selbst erklärt haben, behandelt der Compiler den Fall, in dem nur einer implspeziell gültig ist , und kann diesen verwenden, um die Typinferenz zu steuern:

Hier ist ein Kommentar, der besagt, dass der Compiler "eifrig bestätigt", wenn nur ein Impl gefunden wird, was (unter anderem) deref Zwänge zulässt. Dies ist bei mehreren Impl-Kandidaten nicht der Fall.

Der zweite Teil ist, dass Deref-Zwang nur an Orten auftritt , an denen der erwartete Typ bekannt ist, nicht spekulativ. Siehe Zwangsstellen in der Referenz. Impl Auswahl und Typinferenz müssen zuerst explizit herausfinden, MyAdd::add(&str)was zu erwarten wäre, um zu versuchen, das Argument zu erzwingen &str.

Wenn in dieser Situation eine Problemumgehung erforderlich ist, verwenden Sie einen Ausdruck wie &*boder &b[..]oder b.as_str()für das zweite Argument.

ramslök
quelle