Es ist am einfachsten zu verstehen, was nicht-lexikalische Lebenszeiten sind, indem man versteht, was lexikalische Lebenszeiten sind. In Versionen von Rust, bevor nicht-lexikalische Lebensdauern vorhanden sind, schlägt dieser Code fehl:
fn main() {
let mut scores = vec![1, 2, 3];
let score = &scores[0];
scores.push(4);
}
Der Rust-Compiler sieht, dass scores
er von der score
Variablen entlehnt ist, und lässt daher eine weitere Mutation von scores
:
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let score = &scores[0];
| ------ immutable borrow occurs here
4 | scores.push(4);
| ^^^^^^ mutable borrow occurs here
5 | }
| - immutable borrow ends here
Ein Mensch kann jedoch trivial erkennen, dass dieses Beispiel zu konservativ ist: Es score
wird niemals verwendet ! Das Problem ist , dass der von borrow scores
durch score
ist lexikalischer - es bis zum Ende des Blockes hält , in dem er enthalten ist:
fn main() {
let mut scores = vec![1, 2, 3];
let score = &scores[0];
scores.push(4);
}
Nicht-lexikalische Lebensdauern beheben dies, indem der Compiler verbessert wird, um diesen Detaillierungsgrad zu verstehen. Der Compiler kann jetzt genauer erkennen, wann ein Ausleihen erforderlich ist, und dieser Code wird kompiliert.
Eine wunderbare Sache über nicht-lexikalische Lebenszeiten ist, dass, sobald sie aktiviert sind, niemand mehr an sie denken wird . Es wird einfach "was Rust tut" und die Dinge werden (hoffentlich) einfach funktionieren.
Warum waren lexikalische Lebensdauern erlaubt?
Mit Rust sollen nur bekannte sichere Programme kompiliert werden. Jedoch ist es unmöglich , genau zu erlauben , nur sichere Programme und lehnt unsicher diejenigen. Zu diesem Zweck irrt Rust konservativ: Einige sichere Programme werden abgelehnt. Lexikalische Lebensdauern sind ein Beispiel dafür.
Lexikalische Lebensdauern waren im Compiler viel einfacher zu implementieren, da die Kenntnis von Blöcken "trivial" ist, während die Kenntnis des Datenflusses geringer ist. Der Compiler musste neu geschrieben werden, um eine "Mid-Level Intermediate Representation" (MIR) einzuführen und zu verwenden . Dann musste der Ausleihprüfer (auch bekannt als "Ausleih") neu geschrieben werden, um MIR anstelle des abstrakten Syntaxbaums (AST) zu verwenden. Dann mussten die Regeln des Kreditprüfers verfeinert werden, um feiner zu sein.
Lexikalische Lebensdauern stören den Programmierer nicht immer, und es gibt viele Möglichkeiten, lexikalische Lebensdauern zu umgehen, wenn sie dies tun, auch wenn sie ärgerlich sind. In vielen Fällen mussten zusätzliche geschweifte Klammern oder ein Boolescher Wert hinzugefügt werden. Dies ermöglichte es Rust 1.0, viele Jahre lang zu versenden und nützlich zu sein, bevor nicht-lexikalische Lebensdauern implementiert wurden.
Interessanterweise wurden aufgrund der lexikalischen Lebensdauer bestimmte gute Muster entwickelt. Das beste Beispiel für mich ist das entry
Muster . Dieser Code schlägt vor nicht lexikalischen Lebensdauern fehl und wird damit kompiliert:
fn example(mut map: HashMap<i32, i32>, key: i32) {
match map.get_mut(&key) {
Some(value) => *value += 1,
None => {
map.insert(key, 1);
}
}
}
Dieser Code ist jedoch ineffizient, da er den Hash des Schlüssels zweimal berechnet. Die Lösung, die aufgrund der lexikalischen Lebensdauer erstellt wurde, ist kürzer und effizienter:
fn example(mut map: HashMap<i32, i32>, key: i32) {
*map.entry(key).or_insert(0) += 1;
}
Der Name "nicht-lexikalische Lebenszeiten" klingt für mich nicht richtig
Die Lebensdauer eines Werts ist die Zeitspanne, in der der Wert an einer bestimmten Speicheradresse verbleibt ( eine längere Erklärung finden Sie unter Warum kann ich einen Wert und einen Verweis auf diesen Wert nicht in derselben Struktur speichern? ). Das als nicht-lexikalische Lebensdauern bekannte Merkmal ändert die Lebensdauer von Werten nicht und kann daher die Lebensdauern nicht lexikalisch machen. Es macht nur die Verfolgung und Überprüfung von Krediten dieser Werte genauer.
Ein genauerer Name für die Funktion könnte "nicht lexikalische Ausleihen " sein. Einige Compiler-Entwickler verweisen auf den zugrunde liegenden "MIR-basierten Kredit".
Nicht-lexikalische Lebensdauern wurden nie ein „Benutzer gerichtete“ -Funktion sein soll per se . Sie sind in unseren Köpfen größtenteils groß geworden, weil wir durch ihre Abwesenheit kleine Papierschnitte bekommen. Ihr Name war hauptsächlich für interne Entwicklungszwecke gedacht, und eine Änderung für Marketingzwecke hatte nie Priorität.
Ja, aber wie benutze ich es?
In Rust 1.31 (veröffentlicht am 06.12.2018) müssen Sie sich für die Ausgabe Rust 2018 in Ihrem Cargo.toml anmelden:
[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"
Ab Rust 1.36 ermöglicht die Ausgabe von Rust 2015 auch nicht-lexikalische Lebensdauern.
Die aktuelle Implementierung nicht-lexikalischer Lebensdauern befindet sich in einem "Migrationsmodus". Wenn der NLL-Leihprüfer erfolgreich ist, wird die Kompilierung fortgesetzt. Ist dies nicht der Fall, wird der vorherige Ausleihprüfer aufgerufen. Wenn der alte Leihprüfer den Code zulässt, wird eine Warnung gedruckt, die Sie darüber informiert, dass Ihr Code in einer zukünftigen Version von Rust wahrscheinlich beschädigt wird und aktualisiert werden sollte.
In nächtlichen Versionen von Rust können Sie sich über ein Feature-Flag für den erzwungenen Bruch anmelden:
#![feature(nll)]
Sie können sich sogar mit dem Compiler-Flag für die experimentelle Version von NLL anmelden -Z polonius
.
Eine Auswahl realer Probleme, die durch nicht-lexikalische Lebensdauern gelöst wurden