Was sind nicht-lexikalische Lebensdauern?

87

Rust hat eine RFC zu nicht-lexikalisches Leben bezogen , die genehmigt wurde in der Sprache für eine lange Zeit umgesetzt werden. In letzter Zeit hat sich die Unterstützung dieser Funktion durch Rust erheblich verbessert und gilt als vollständig.

Meine Frage ist: Was genau ist ein nicht lexikalisches Leben?

Stargateur
quelle

Antworten:

129

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 scoreser von der scoreVariablen 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 scorewird niemals verwendet ! Das Problem ist , dass der von borrow scoresdurch scoreist 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);                 //
                                    // <-- score stops borrowing here
}

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 entryMuster . 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 <[email protected]>"]
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

Shepmaster
quelle
11
Ich denke, es wäre hervorzuheben, dass es bei nicht-lexikalischen Lebensdauern möglicherweise nicht intuitiv um die Lebensdauer von Variablen geht, sondern um die Lebensdauer von Krediten. Oder, anders gesagt, bei nicht-lexikalischen Lebensdauern geht es darum, die Lebensdauern von Variablen von denen von Krediten zu dekorrelieren ... es sei denn, ich irre mich? (aber ich glaube nicht, dass sich NLL ändert, wenn ein Destruktor ausgeführt wird)
Matthieu M.
2
" Interessanterweise wurden bestimmte gute Muster aufgrund lexikalischer Lebensdauern entwickelt. " - Ich nehme also an, dass das Risiko besteht, dass die Existenz von NLL zukünftige gute Muster so viel schwieriger zu identifizieren macht.
Eggyal
1
@eggyal es ist sicherlich eine Möglichkeit. Das Entwerfen innerhalb einer Reihe von Einschränkungen (auch wenn willkürlich!) Kann zu neuen, interessanten Entwürfen führen. Ohne diese Einschränkungen könnten wir auf unser vorhandenes Wissen und unsere vorhandenen Muster zurückgreifen und niemals lernen oder erforschen, um etwas Neues zu finden. Davon abgesehen würde vermutlich jemand denken "Oh, der Hash wird zweimal berechnet, das kann ich beheben" und die API würde erstellt, aber es könnte für Benutzer schwieriger sein, die API überhaupt zu finden. Ich hoffe, dass Tools wie Clippy diesen Leuten helfen.
Shepmaster