Dave Hermans jüngster Vortrag in Rust sagte, dass sie diese Eigenschaft von C ++ ausgeliehen haben. Ich konnte nichts rund um das Thema finden. Kann jemand bitte erklären, was Monomorphisierung bedeutet?
Dave Hermans jüngster Vortrag in Rust sagte, dass sie diese Eigenschaft von C ++ ausgeliehen haben. Ich konnte nichts rund um das Thema finden. Kann jemand bitte erklären, was Monomorphisierung bedeutet?
Antworten:
Monomorphisierung bedeutet, spezielle Versionen generischer Funktionen zu generieren. Wenn ich eine Funktion schreibe, die das erste Element eines Paares extrahiert:
fn first<A, B>(pair: (A, B)) -> A {
let (a, b) = pair;
return a;
}
und dann rufe ich diese Funktion zweimal auf:
first((1, 2));
first(("a", "b"));
Der Compiler generiert zwei Versionen von first()
, eine für Paare von Ganzzahlen und eine für Paare von Zeichenfolgen.
Der Name leitet sich vom Programmiersprachenbegriff "Polymorphismus" ab - was eine Funktion bedeutet, die mit vielen Arten von Daten umgehen kann. Monomorphisierung ist die Umwandlung von polymorphem in monomorphen Code.
Ich bin mir nicht sicher, ob sich noch jemand damit befasst, aber in der Rust-Dokumentation wird tatsächlich erwähnt, wie durch diesen Prozess keine Kostenabstraktion erzielt wird. Aus der Leistung von Code unter Verwendung von Generika :
Möglicherweise fragen Sie sich, ob bei Verwendung generischer Typparameter Laufzeitkosten anfallen. Die gute Nachricht ist, dass Rust Generika so implementiert, dass Ihr Code mit generischen Typen nicht langsamer ausgeführt wird als mit konkreten Typen.
Rust erreicht dies, indem es eine Monomorphisierung des Codes durchführt, der zur Kompilierungszeit Generika verwendet. Bei der Monomorphisierung wird generischer Code in spezifischen Code umgewandelt, indem die konkreten Typen ausgefüllt werden, die beim Kompilieren verwendet werden.
In diesem Prozess führt der Compiler das Gegenteil der Schritte aus, die wir zum Erstellen der generischen Funktion in Listing 10-5 verwendet haben: Der Compiler überprüft alle Stellen, an denen generischer Code aufgerufen wird, und generiert Code für die konkreten Typen, mit denen der generische Code aufgerufen wird .
Schauen wir uns an einem Beispiel an, das die Option enum der Standardbibliothek verwendet:
let integer = Some(5); let float = Some(5.0);
Wenn Rust diesen Code kompiliert, führt er eine Monomorphisierung durch. Während dieses Vorgangs liest der Compiler die Werte, die in Optionsinstanzen verwendet wurden, und identifiziert zwei Arten von Optionen: eine ist i32 und die andere ist f64. Daher wird die generische Definition von Option in Option_i32 und Option_f64 erweitert, wodurch die generische Definition durch die spezifischen ersetzt wird.
Die monomorphisierte Version des Codes sieht wie folgt aus. Die generische Option wird durch die vom Compiler erstellten spezifischen Definitionen ersetzt:
// Filename: src/main.rs enum Option_i32 { Some(i32), None, } enum Option_f64 { Some(f64), None, } fn main() { let integer = Option_i32::Some(5); let float = Option_f64::Some(5.0); }
Da Rust generischen Code in Code kompiliert, der den Typ in jeder Instanz angibt, zahlen wir keine Laufzeitkosten für die Verwendung von Generika. Wenn der Code ausgeführt wird, funktioniert er genauso, als ob wir jede Definition von Hand dupliziert hätten. Der Prozess der Monomorphisierung macht die Generika von Rust zur Laufzeit äußerst effizient.
Ich bin mir nicht sicher. Könntest du auf das Gespräch verlinken? Es könnte eine beiläufige Bemerkung gewesen sein.
Herman könnte einen Begriff für so etwas wie eine Vorlagenspezialisierung geprägt haben, die aus der Vorlage, die eine polymorphe Struktur darstellt, Typen / Objekte erzeugt, die nicht miteinander zusammenhängen (nicht polymorph oder "monomorph").
Es gibt eine schöne Erklärung der Monomorphisierung im Rostbuch
Bei der Monomorphisierung wird generischer Code in spezifischen Code umgewandelt, indem die konkreten Typen ausgefüllt werden, die beim Kompilieren verwendet werden.
Wenn Sie im Buchbeispiel Variablen definiert haben mit Some
:
let integer = Some(5); let float = Some(5.0);
Wenn Rust diesen Code kompiliert, führt er eine Monomorphisierung durch. Während dieses Prozesses liest der Compiler die Werte, die in
Option<T>
Instanzen verwendet wurden, und identifiziert zwei Arten vonOption<T>
: eine isti32
und die andere istf64
. Als solches erweitert es die generische Definition vonOption<T>
inOption_i32
und ersetztOption_f64
dadurch die generische Definition durch die spezifischen.Die monomorphisierte Version des Codes sieht wie folgt aus. Das Generikum
Option<T>
wird durch die vom Compiler erstellten spezifischen Definitionen ersetzt:Dateiname: src / main.rs
enum Option_i32 { Some(i32), None, } enum Option_f64 { Some(f64), None, } fn main() { let integer = Option_i32::Some(5); let float = Option_f64::Some(5.0); }