Cosa sono le vite non lessicali?


Risposte:


139

È più facile capire cosa sono le vite non lessicali comprendendo cosa sono le vite lessicali . Nelle versioni di Rust prima che siano presenti vite non lessicali, questo codice fallirà:

fn main() {
    let mut scores = vec![1, 2, 3];
    let score = &scores[0];
    scores.push(4);
}

Il compilatore Rust vede che scoresè preso in prestito dalla scorevariabile, quindi non consente ulteriori modifiche di 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

Tuttavia, un essere umano può banalmente vedere che questo esempio è eccessivamente conservatore: nonscore viene mai usato ! Il problema è che il prestito discores by scoreè lessicale - dura fino alla fine del blocco in cui è contenuto:

fn main() {
    let mut scores = vec![1, 2, 3]; //
    let score = &scores[0];         //
    scores.push(4);                 //
                                    // <-- score stops borrowing here
}

Le vite non lessicali risolvono questo problema migliorando il compilatore per comprendere questo livello di dettaglio. Il compilatore ora può dire più accuratamente quando è necessario un prestito e questo codice verrà compilato.

Una cosa meravigliosa delle vite non lessicali è che una volta abilitate, nessuno ci penserà mai . Diventerà semplicemente "quello che fa Rust" e le cose (si spera) funzioneranno.

Perché sono state consentite vite lessicali?

Rust ha lo scopo di consentire la compilazione solo di programmi sicuri. Tuttavia è impossibile consentire esattamente solo programmi sicuri e rifiutare quelli non sicuri. A tal fine, Rust sbaglia per essere conservatore: alcuni programmi sicuri vengono rifiutati. Le vite lessicali ne sono un esempio.

Le vite lessicali erano molto più facili da implementare nel compilatore perché la conoscenza dei blocchi è "banale", mentre la conoscenza del flusso di dati lo è meno. Il compilatore doveva essere riscritto per introdurre e utilizzare una "rappresentazione intermedia di medio livello" (MIR) . Quindi è stato necessario riscrivere il controllo dei prestiti (noto anche come "prestito in prestito") per utilizzare MIR al posto dell'albero della sintassi astratta (AST). Quindi le regole del controllo dei prestiti dovevano essere raffinate per essere più fini.

Le vite lessicali non sempre intralciano il programmatore e ci sono molti modi per aggirare le vite lessicali quando lo fanno, anche se sono fastidiose. In molti casi, ciò comportava l'aggiunta di parentesi graffe extra o un valore booleano. Ciò ha permesso a Rust 1.0 di essere distribuito ed essere utile per molti anni prima che venissero implementate vite non lessicali.

È interessante notare che alcuni buoni modelli sono stati sviluppati a causa delle vite lessicali. Il primo esempio per me è il entrymodello . Questo codice fallisce prima della vita non lessicale e viene compilato con esso:

fn example(mut map: HashMap<i32, i32>, key: i32) {
    match map.get_mut(&key) {
        Some(value) => *value += 1,
        None => {
            map.insert(key, 1);
        }
    }
}

Tuttavia, questo codice è inefficiente perché calcola l'hash della chiave due volte. La soluzione che è stata creata a causa della vita lessicale è più breve ed efficiente:

fn example(mut map: HashMap<i32, i32>, key: i32) {
    *map.entry(key).or_insert(0) += 1;
}

Il nome "vite non lessicali" non mi suona bene

La durata di un valore è il periodo di tempo durante il quale il valore rimane in un indirizzo di memoria specifico (vedere Perché non è possibile memorizzare un valore e un riferimento a quel valore nella stessa struttura? Per una spiegazione più lunga). La funzione nota come vite non lessicali non cambia le vite di alcun valore, quindi non può rendere le vite non lessicali. Rende solo più preciso il monitoraggio e il controllo dei prestiti di quei valori.

Un nome più accurato per la funzione potrebbe essere "non-lessicali prende in prestito ". Alcuni sviluppatori di compilatori fanno riferimento al sottostante "prestito basato su MIR".

Le vite non lessicali non sono mai state intese come una caratteristica "rivolta all'utente", di per sé . Sono diventati per lo più grandi nelle nostre menti a causa dei piccoli ritagli di carta che otteniamo dalla loro assenza. Il loro nome era principalmente destinato a scopi di sviluppo interno e cambiarlo per scopi di marketing non era mai una priorità.

Sì, ma come lo uso?

In Rust 1.31 (rilasciato il 06-12-2018), devi iscriverti all'edizione Rust 2018 nel tuo Cargo.toml:

[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"

A partire da Rust 1.36, l'edizione Rust 2015 consente anche durate non lessicali.

L'attuale implementazione di vite non lessicali è in una "modalità di migrazione". Se il controllo del prestito NLL viene superato, la compilazione continua. In caso contrario, viene richiamato il precedente controllo del prestito. Se il vecchio verificatore di prestiti consente il codice, viene stampato un avviso che informa che è probabile che il codice non funzioni correttamente in una versione futura di Rust e che dovrebbe essere aggiornato.

Nelle versioni notturne di Rust, puoi attivare l'interruzione forzata tramite un flag di funzionalità:

#![feature(nll)]

Puoi anche opt-in per la versione sperimentale di NLL utilizzando il flag del compilatore -Z polonius.

Un esempio di problemi reali risolti da vite non lessicali


12
Penso che varrebbe la pena sottolineare che, forse controintuitivamente, le vite non lessicali non riguardano la durata delle variabili, ma la durata dei prestiti. O, altrimenti detto, Non-Lexical Lifetimes riguarda la decorrelazione della durata delle variabili da quella dei prestiti ... a meno che non mi sbagli? (ma non credo che NLL cambi quando viene eseguito un distruttore)
Matthieu M.

2
" È interessante notare che alcuni buoni modelli sono stati sviluppati a causa di vite lessicali " - Suppongo, quindi, che ci sia il rischio che l'esistenza di NLL possa rendere i buoni modelli futuri molto più difficili da identificare?
eggyal

1
@eggyal è sicuramente una possibilità. Progettare all'interno di una serie di vincoli (anche se arbitrari!) Può portare a progetti nuovi e interessanti. Senza questi vincoli, potremmo ripiegare sulle nostre conoscenze e modelli esistenti e non imparare o esplorare mai per trovare qualcosa di nuovo. Detto questo, presumibilmente qualcuno penserebbe "oh, l'hash viene calcolato due volte, posso risolverlo" e l'API verrebbe creata, ma potrebbe essere più difficile per gli utenti trovare l'API in primo luogo. Spero che strumenti come Clippy aiutino quelle persone.
Shepmaster,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.