Quali sono le vite non lessicali?

Rust ha una RFC relativa a vite non lessicali che è stata approvata per essere implementata nella lingua per un lungo periodo. Recentemente , il supporto di Rust a questa funzione è migliorato molto ed è considerato completo.

La mia domanda è: che cosa è esattamente una vita non lessicale?

È più facile capire quali sono le vite non lessicali comprendendo quali 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 di Rust vede che i scores sono presi in prestito dalla variabile score , quindi non consente ulteriori mutazioni dei 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 umano può banalmente vedere che questo esempio è eccessivamente conservativo: il score non è mai usato ! Il problema è che il prestito dei scores per 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. Ora il compilatore può dire con maggiore precisione quando è necessario un prestito e questo codice verrà compilato.

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

Perché sono state permesse le vite lessicali?

Rust ha lo scopo di consentire solo la compilazione di programmi sicuri. Tuttavia, è imansible consentire esattamente solo i programmi sicuri e rifiutare quelli non sicuri. A tal fine, Rust sbaglia dalla parte del conservatore: alcuni programmi sicuri vengono respinti. Le vite lessicali sono un esempio di questo.

Le vite lessicali erano molto più facili da implementare nel compilatore perché la conoscenza dei blocchi è "banale", mentre la conoscenza del stream di dati è minore. Il compilatore doveva essere riscritto per introdurre e utilizzare una "rappresentazione intermedia di medio livello" (MIR) . Quindi il controllore del prestito (aka "borrowck") doveva essere riscritto per usare MIR invece dell'albero di syntax astratto (AST). Quindi le regole del controllore del prestito 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ò consentiva a Rust 1.0 di essere disponibile e di 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 modello di entry . Questo codice fallisce prima delle vite non lessicali e si compila con esso:

 fn example(mut map: HashMap, 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 delle vite lessicali è più breve e più efficiente:

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

Il nome "vite non lessicali" non mi sembra giusto

La durata di un valore è l'intervallo di tempo durante il quale il valore rimane in uno specifico indirizzo di memoria (vedere Perché non posso memorizzare un valore e un riferimento a quel valore nella stessa struttura? Per una spiegazione più lunga). La caratteristica nota come vite non lessicali non modifica la durata di alcun valore, quindi non può rendere le vite non lessicali. Rende più preciso il monitoraggio e il controllo dei prestiti di tali valori.

Un nome più preciso per la funzione potrebbe essere "non-lessico". Alcuni sviluppatori di compilatori si riferiscono al sottostante "borrowck basato su MIR".

Le vite non lessicali non sono mai state intese come una caratteristica "user-facing", di per sé . Sono per lo più cresciuti nelle nostre menti a causa dei piccoli graffi che otteniamo dalla loro assenza. Il loro nome era principalmente inteso per scopi di sviluppo interno e il suo cambiamento a fini di marketing non era mai una priorità.

Sì, ma come lo uso?

Non sei nella rust stabile. Ad un certo punto sarà abilitato a chiunque lo utilizzerà e non ci sarà nulla che tu debba fare.

Nelle versioni notturne di Rust, puoi triggersre l'NLL tramite una funzione:

 #![feature(nll)] 

Puoi persino optare per la versione sperimentale di NLL con il flag del compilatore -Z polonius .

Un campione di problemi reali risolti da vite non lessicali

  • Il prestito di una HashMap dura oltre lo scopo in cui si trova?
  • Perché HashMap :: get_mut () diventa proprietario della mappa per il resto dell'ambito?
  • Non si può prendere in prestito come immutabile perché è anche preso in prestito come mutabile negli argomenti della funzione
  • Come aggiornare o inserire su un Vec?
  • C'è un modo per rilasciare un'associazione prima che esca dall'ambito di applicazione?
  • Non è ansible ottenere un riferimento mutabile durante l'iterazione di una struttura ricorsiva: non può prendere in prestito come mutabile più di una volta alla volta
  • Quando si restituisce il risultato del consumo di uno StdinLock, perché il prestito è stato trattenuto in stdin?
  • Errore collateralmente spostato quando si decostruisce una scatola di coppie