Paling mudah untuk memahami apa itu kehidupan non-leksikal dengan memahami apa itu kehidupan leksikal . Dalam versi Rust sebelum masa aktif non-leksikal ada, kode ini akan gagal:
fn main() {
let mut scores = vec![1, 2, 3];
let score = &scores[0];
scores.push(4);
}
Kompilator Rust melihat yang scores
dipinjam oleh score
variabel, jadi itu melarang mutasi lebih lanjut dari 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
Namun, manusia sepele dapat melihat bahwa contoh ini adalah terlalu konservatif: score
ini tidak pernah digunakan ! Masalahnya adalah bahwa peminjaman scores
oleh score
adalah leksikal - itu berlangsung sampai akhir blok di mana ia terkandung:
fn main() {
let mut scores = vec![1, 2, 3];
let score = &scores[0];
scores.push(4);
}
Umur non-leksikal memperbaikinya dengan meningkatkan kompilator untuk memahami tingkat detail ini. Kompilator sekarang dapat lebih akurat mengetahui kapan pinjaman diperlukan dan kode ini akan dikompilasi.
Hal yang menakjubkan tentang kehidupan non-leksikal adalah bahwa sekali diaktifkan, tidak ada yang akan memikirkannya . Ini hanya akan menjadi "apa yang Rust lakukan" dan segala sesuatunya akan (mudah-mudahan) bekerja begitu saja.
Mengapa kehidupan leksikal diperbolehkan?
Rust dimaksudkan untuk hanya mengizinkan program yang diketahui aman untuk dikompilasi. Namun, tidak mungkin untuk hanya mengizinkan program yang aman dan menolak program yang tidak aman. Untuk itu, Rust melakukan kesalahan dengan bersikap konservatif: beberapa program yang aman ditolak. Kehidupan leksikal adalah salah satu contohnya.
Masa pakai leksikal jauh lebih mudah untuk diterapkan di kompiler karena pengetahuan tentang blok "sepele", sedangkan pengetahuan tentang aliran data kurang begitu. Kompilator perlu ditulis ulang untuk memperkenalkan dan menggunakan "representasi perantara tingkat menengah" (MIR) . Kemudian pemeriksa peminjam (alias "pinjam") harus ditulis ulang untuk menggunakan MIR, bukan pohon sintaks abstrak (AST). Kemudian aturan pemeriksa pinjaman harus disempurnakan agar lebih halus.
Kehidupan leksikal tidak selalu menghalangi pemrogram, dan ada banyak cara untuk mengatasi masa hidup leksikal ketika mereka melakukannya, bahkan jika mereka mengganggu. Dalam banyak kasus, ini melibatkan penambahan tanda kurung kurawal atau nilai boolean. Hal ini memungkinkan Rust 1.0 untuk dikirimkan dan berguna selama bertahun-tahun sebelum masa pakai non-leksikal diterapkan.
Menariknya, pola-pola tertentu yang baik dikembangkan karena masa kehidupan leksikal. Contoh utama bagi saya adalah yang entry
pola . Kode ini gagal sebelum masa hidup non-leksikal dan dikompilasinya:
fn example(mut map: HashMap<i32, i32>, key: i32) {
match map.get_mut(&key) {
Some(value) => *value += 1,
None => {
map.insert(key, 1);
}
}
}
Namun, kode ini tidak efisien karena menghitung hash kunci dua kali. Solusi yang dibuat karena masa pakai leksikal lebih pendek dan lebih efisien:
fn example(mut map: HashMap<i32, i32>, key: i32) {
*map.entry(key).or_insert(0) += 1;
}
Nama "masa hidup non-leksikal" terdengar tidak tepat bagi saya
Umur nilai adalah rentang waktu di mana nilai tetap berada di alamat memori tertentu (lihat Mengapa saya tidak bisa menyimpan nilai dan referensi ke nilai itu di struct yang sama? Untuk penjelasan yang lebih panjang). Fitur yang dikenal sebagai masa hidup non-leksikal tidak mengubah masa hidup nilai apa pun, sehingga tidak dapat membuat masa hidup menjadi non-leksikal. Itu hanya membuat pelacakan dan pemeriksaan pinjaman dari nilai-nilai itu lebih tepat.
Sebuah nama yang lebih akurat untuk fitur mungkin "non-leksikal Borrows ". Beberapa pengembang kompilator merujuk pada "pinjaman berbasis MIR" yang mendasarinya.
Masa pakai non-leksikal tidak pernah dimaksudkan sebagai fitur "yang dihadapi pengguna", per se . Mereka sebagian besar tumbuh besar dalam pikiran kami karena potongan kertas kecil yang kami dapatkan dari ketidakhadiran mereka. Nama mereka sebagian besar ditujukan untuk tujuan pengembangan internal dan mengubahnya untuk tujuan pemasaran tidak pernah menjadi prioritas.
Ya, tapi bagaimana cara menggunakannya?
Di Rust 1.31 (dirilis pada 2018-12-06), Anda harus ikut serta ke edisi Rust 2018 di Cargo.toml Anda:
[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"
Mulai Rust 1.36, edisi Rust 2015 juga memungkinkan masa hidup non-leksikal.
Implementasi masa hidup non-leksikal saat ini berada dalam "mode migrasi". Jika pemeriksa peminjam NLL lolos, kompilasi dilanjutkan. Jika tidak, pemeriksa pinjaman sebelumnya akan dipanggil. Jika pemeriksa pinjaman lama mengizinkan kode tersebut, peringatan akan dicetak, memberi tahu Anda bahwa kode Anda kemungkinan besar rusak di versi Rust mendatang dan harus diperbarui.
Dalam versi malam Rust, Anda dapat ikut serta dalam kerusakan yang diberlakukan melalui tanda fitur:
#![feature(nll)]
Anda bahkan dapat ikut serta ke versi eksperimental NLL dengan menggunakan tanda compiler -Z polonius
.
Contoh masalah nyata yang diselesaikan oleh masa hidup non-leksikal