Warum ist `std :: mem :: drop` nicht genau dasselbe wie der Abschluss | _ | () in höherrangigen Merkmalsgrenzen?


13

Die Implementierung von std::mem::dropist wie folgt dokumentiert:

pub fn drop<T>(_x: T) { }

Als solches würde ich erwarten, dass der Verschluss |_| ()(umgangssprachlich als Toilettenverschluss bezeichnet ) ein potenzieller 1: 1-Ersatz für dropbeide Richtungen ist. Der folgende Code zeigt jedoch, dass er dropnicht mit einem höherrangigen Merkmal kompatibel ist , das an den Parameter der Funktion gebunden ist, wohingegen der Toilettenverschluss dies ist.

fn foo<F, T>(f: F, x: T)
where
    for<'a> F: FnOnce(&'a T),
{
    dbg!(f(&x));
}

fn main() {
    foo(|_| (), "toilet closure"); // this compiles
    foo(drop, "drop"); // this does not!
}

Die Fehlermeldung des Compilers:

error[E0631]: type mismatch in function arguments
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^
   |     |
   |     expected signature of `for<'a> fn(&'a _) -> _`
   |     found signature of `fn(_) -> _`

error[E0271]: type mismatch resolving `for<'a> <fn(_) {std::mem::drop::<_>} as std::ops::FnOnce<(&'a _,)>>::Output == ()`
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^ expected bound lifetime parameter 'a, found concrete lifetime

In Anbetracht dessen, dass dies in dropBezug auf jede Größe angeblich generisch ist T, klingt es unvernünftig, dass die "allgemeinere" Signatur fn(_) -> _nicht mit kompatibel ist for<'a> fn (&'a _) -> _. Warum lässt der Compiler die Unterschrift von drophier nicht zu und was unterscheidet ihn, wenn der Toilettenverschluss an seiner Stelle platziert wird?

Antworten:


4

Der Kern des Problems besteht darin, dass dropes sich nicht um eine einzelne Funktion handelt, sondern um einen parametrisierten Satz von Funktionen, die jeweils einen bestimmten Typ löschen. Um eine höherrangige Merkmalsbindung (im Folgenden: hrtb) zu erfüllen, benötigen Sie eine einzelne Funktion, die gleichzeitig Verweise auf einen Typ mit einer bestimmten Lebensdauer aufnehmen kann.


Wir werden dropals typisches Beispiel für eine generische Funktion verwenden, aber all dies gilt auch allgemeiner. Hier ist der Code als Referenz : fn drop<T>(_: T) {}.

Konzeptionell drophandelt es sich nicht um eine einzelne Funktion, sondern um eine Funktion für jeden möglichen Typ T. Jede bestimmte Instanz von dropakzeptiert nur Argumente eines einzelnen Typs. Dies nennt man Monomorphisierung . Wenn eine andere Version Tverwendet wird drop, wird eine andere Version von dropkompiliert. Aus diesem Grund können Sie eine generische Funktion nicht als Argument übergeben und diese Funktion allgemein verwenden (siehe diese Frage ).

Andererseits fn pass(x: &i32) -> &i32 {x}erfüllt eine Funktion wie die hrtb for<'a> Fn(&'a i32) -> &'a i32. Im Gegensatz dazu drophaben wir eine einzige Funktion, die gleichzeitig Fn(&'a i32) -> &'a i32für jedes Leben erfüllt 'a. Dies spiegelt sich in der Verwendbarkeit wider pass.

fn pass(x: &i32) -> &i32 {
    x
}

fn two_uses<F>(f: F)
where
    for<'a> F: Fn(&'a i32) -> &'a i32, // By the way, this can simply be written
                                       // F: Fn(&i32) -> &i32 due to lifetime elision rules.
                                       // That applies to your original example too.
{
    {
        // x has some lifetime 'a
        let x = &22;
        println!("{}", f(x));
        // 'a ends around here
    }
    {
        // y has some lifetime 'b
        let y = &23;
        println!("{}", f(y));
        // 'b ends around here
    }
    // 'a and 'b are unrelated since they have no overlap
}

fn main() {
    two_uses(pass);
}

(Spielplatz)

Im Beispiel sind die Lebensdauern 'aund 'bhaben keine Beziehung zueinander: keiner umfasst den anderen vollständig. Es gibt hier also keine Subtypisierung. Eine einzelne Instanz von passwird tatsächlich mit zwei unterschiedlichen, nicht miteinander verbundenen Lebensdauern verwendet.

Deshalb dropbefriedigt nicht for<'a> FnOnce(&'a T). Eine bestimmte Instanz von dropkann nur eine Lebensdauer abdecken (ohne Subtypisierung). Wenn wir passierten dropin two_usesaus dem obigen Beispiel (mit geringfügigen Änderungen Unterschrift und unter der Annahme der Compiler uns lassen), wäre es müssen einige besondere Lebenszeit wählen 'aund die Instanz dropim Rahmen two_useswäre Fn(&'a i32)für einige konkrete Lebensdauer 'a. Da die Funktion nur für eine einzelne Lebensdauer gelten würde 'a, wäre es nicht möglich, sie mit zwei nicht miteinander verbundenen Lebensdauern zu verwenden.

Warum bekommt der Toilettenverschluss ein HRTB? Wenn der erwartete Typ beim Ableiten des Typs für einen Abschluss darauf hinweist, dass eine höherrangige Merkmalsbindung erforderlich ist, versucht der Compiler, eine Anpassung vorzunehmen . In diesem Fall ist es erfolgreich.


Das Problem Nr. 41078 ist eng damit verbunden, und insbesondere gibt der Kommentar von eddyb hier im Wesentlichen die obige Erklärung (allerdings im Zusammenhang mit Schließungen und nicht mit gewöhnlichen Funktionen). Das Problem selbst behebt das derzeitige Problem jedoch nicht. Stattdessen wird erläutert, was passiert, wenn Sie den Toilettenverschluss vor der Verwendung einer Variablen zuweisen (probieren Sie es aus!).

Es ist möglich, dass sich die Situation in Zukunft ändern wird, aber es würde eine ziemlich große Änderung in der Art und Weise erfordern, wie generische Funktionen monomorphisiert werden.


4

Kurz gesagt, beide Leitungen sollten ausfallen. Da jedoch ein Schritt in der alten Art der Behandlung von HRTB-Lebensdauern, nämlich die Dichtheitsprüfung , derzeit ein Problem mit der Solidität aufweist, rustcwird (fälschlicherweise) einer akzeptiert und der andere mit einer ziemlich schlechten Fehlermeldung zurückgelassen.

Wenn Sie die Dichtheitsprüfung mit deaktivieren rustc +nightly -Zno-leak-check, wird eine sinnvollere Fehlermeldung angezeigt:

error[E0308]: mismatched types
  --> src/main.rs:10:5
   |
10 |     foo(drop, "drop");
   |     ^^^ one type is more general than the other
   |
   = note: expected type `std::ops::FnOnce<(&'a &str,)>`
              found type `std::ops::FnOnce<(&&str,)>`

Meine Interpretation dieses Fehlers ist, dass der &xim Körper der fooFunktion nur eine auf diesen Körper beschränkte f(&x)Gültigkeitsdauer des Gültigkeitsbereichs hat , also auch dieselbe Gültigkeitsdauer des Gültigkeitsbereichs, die möglicherweise nicht die for<'a>universelle Quantifizierung erfüllen kann, die für das gebundene Merkmal erforderlich ist.

Die Frage, die Sie hier stellen, ist fast identisch mit der Ausgabe Nr. 57642 , die ebenfalls zwei kontrastierende Teile enthält.

Die neue Art, hrtb-Lebensdauern zu verarbeiten, ist die Verwendung sogenannter Universen . Niko hat einen WIP , um die Dichtheitsprüfung mit Universen anzugehen. Im Rahmen dieser neuen Regelung, beide Teile der Ausgabe # 57642 oben verbunden wird gesagt, alle scheitern mit weit mehr klar Diagnosen. Ich nehme an, der Compiler sollte bis dahin auch in der Lage sein, Ihren Beispielcode korrekt zu verarbeiten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.