È 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 score
variabile, 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);
}
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 entry
modello . 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