for<>
Die Syntax wird als höherrangiges Trait Bound (HRTB) bezeichnet und wurde in der Tat hauptsächlich aufgrund von Schließungen eingeführt.
Kurz gesagt, der Unterschied zwischen foo
und bar
besteht darin, dass in foo()
der Lebensdauer für die interne usize
Referenz vom Aufrufer der Funktion bereitgestellt wird , während in bar()
derselben Lebensdauer die Funktion selbst bereitgestellt wird . Und diese Unterscheidung ist sehr wichtig für die Umsetzung von foo
/ bar
.
In diesem speziellen Fall Trait
ist diese Unterscheidung jedoch sinnlos , wenn keine Methoden vorhanden sind, die den Typparameter verwenden. Stellen Sie sich also Trait
Folgendes vor:
trait Trait<T> {
fn do_something(&self, value: T);
}
Denken Sie daran, dass die Lebensdauerparameter den generischen Typparametern sehr ähnlich sind. Wenn Sie eine generische Funktion verwenden, geben Sie immer alle Typparameter an und geben konkrete Typen an. Der Compiler monomorphisiert die Funktion. Gleiches gilt für Lebensdauerparameter: Wenn Sie eine Funktion mit einem Lebensdauerparameter aufrufen, geben Sie die Lebensdauer an, wenn auch implizit:
'a: {
foo::<'a>(Box::new(TraitImpl::new::<&'a usize>()));
}
Und jetzt gibt es eine Einschränkung, was foo()
mit diesem Wert geschehen kann, dh mit welchen Argumenten er aufgerufen werden kann do_something()
. Dies wird beispielsweise nicht kompiliert:
fn foo<'a>(b: Box<Trait<&'a usize>>) {
let x: usize = 10;
b.do_something(&x);
}
Dies wird nicht kompiliert, da lokale Variablen eine Lebensdauer haben, die streng kleiner ist als die durch die Lebensdauerparameter angegebene Lebensdauer (ich denke, es ist klar, warum dies so ist). Sie können daher nicht aufrufen, b.do_something(&x)
da für das Argument eine Lebensdauer erforderlich 'a
ist streng größer als das von x
.
Sie können dies jedoch tun mit bar
:
fn bar(b: Box<for<'a> Trait<&'a usize>>) {
let x: usize = 10;
b.do_something(&x);
}
Dies funktioniert, da jetzt bar
anstelle des Anrufers von die erforderliche Lebensdauer ausgewählt werden kann bar
.
Dies ist wichtig, wenn Sie Verschlüsse verwenden, die Referenzen akzeptieren. Angenommen, Sie möchten eine filter()
Methode schreiben für Option<T>
:
impl<T> Option<T> {
fn filter<F>(self, f: F) -> Option<T> where F: FnOnce(&T) -> bool {
match self {
Some(value) => if f(&value) { Some(value) } else { None }
None => None
}
}
}
Der Abschluss hier muss einen Verweis auf akzeptieren, T
da es sonst unmöglich wäre, den in der Option enthaltenen Wert zurückzugeben (dies ist die gleiche Begründung wie bei filter()
Iteratoren).
Aber was Lebensdauer soll &T
in FnOnce(&T) -> bool
haben? Denken Sie daran, dass wir in Funktionssignaturen keine Lebensdauern angeben, nur weil eine Lebensdauerelision vorhanden ist. Tatsächlich fügt der Compiler für jede Referenz einen Lebensdauerparameter in eine Funktionssignatur ein. Es sollte eine gewisse Lebensdauer mit &T
in verbunden sein FnOnce(&T) -> bool
. Der "offensichtlichste" Weg, um die Signatur oben zu erweitern, wäre folgender:
fn filter<'a, F>(self, f: F) -> Option<T> where F: FnOnce(&'a T) -> bool
Dies wird jedoch nicht funktionieren. Wie im Beispiel mit Trait
oben, Lebensdauer 'a
ist streng länger als die Lebensdauer einer lokalen Variablen in dieser Funktion, einschließlich der value
in der Spiel - Anweisung. Daher ist es nicht möglich anzuwenden , f
um &value
wegen der Lebensdauer stimmt nicht überein. Die obige Funktion, die mit einer solchen Signatur geschrieben wurde, wird nicht kompiliert.
Auf der anderen Seite, wenn wir die Signatur von so erweitern filter()
(und so funktioniert die lebenslange Elision für Verschlüsse jetzt in Rust):
fn filter<F>(self, f: F) -> Option<T> where F: for<'a> FnOnce(&'a T) -> bool
Dann ist das Aufrufen f
mit &value
als Argument vollkommen gültig: Wir können jetzt die Lebensdauer auswählen, daher ist die Verwendung der Lebensdauer einer lokalen Variablen absolut in Ordnung. Und deshalb sind HRTBs wichtig: Ohne sie können Sie nicht viele nützliche Muster ausdrücken.
Sie können auch eine andere Erklärung zu HRTBs in Nomicon lesen .
for
, wenn sie verfügbar wären:for<T> Monad<T>
oder zumindest ähnliche Konzepte haben - eine unendliche Anzahl von Merkmalsgrenzen (oder Typen, im Fall von HKTs), die mit etwas parametrisiert sind (Lebensdauern oder Typen) ). Es ist jedoch denkbar, dass HRTBs auch Typen von Lebensdauern unterstützen. Es ist nur so, dass noch niemand ein konkretes Design entwickelt hat.QuantifiedConstraints
?