Apa itu kehidupan non-leksikal?

93

Rust memiliki RFC yang terkait dengan masa hidup non-leksikal yang telah disetujui untuk diimplementasikan dalam bahasa tersebut sejak lama. Baru-baru ini , dukungan Rust tentang fitur ini telah meningkat banyak dan dianggap selesai.

Pertanyaan saya adalah: apa sebenarnya kehidupan non-leksikal itu?

Stargateur
sumber

Jawaban:

139

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 scoresdipinjam oleh scorevariabel, 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: scoreini tidak pernah digunakan ! Masalahnya adalah bahwa peminjaman scoresoleh scoreadalah 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);                 //
                                    // <-- score stops borrowing here
}

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

Shepmaster
sumber
12
Saya pikir perlu ditekankan bahwa, mungkin secara kontra-intuitif, Non-Lexical Lifetimes bukan tentang Lifetime variabel, tetapi tentang Lifetime of Borrows. Atau, sebaliknya dikatakan, Non-Lexical Lifetimes adalah tentang menghias masa hidup variabel dari yang meminjam ... kecuali saya salah? (tapi saya tidak berpikir bahwa NLL berubah ketika destruktor dijalankan)
Matthieu M.
2
" Menariknya, pola-pola tertentu yang baik dikembangkan karena masa hidup leksikal " —Saya kira, kemudian, ada risiko bahwa keberadaan NLL dapat membuat pola-pola baik di masa depan jauh lebih sulit untuk diidentifikasi?
eggyal
1
@eggyal itu pasti kemungkinan. Mendesain dalam sekumpulan batasan (meskipun sewenang-wenang!) Dapat menghasilkan desain baru yang menarik. Tanpa kendala tersebut, kita mungkin akan kembali pada pengetahuan dan pola yang ada dan tidak pernah belajar atau mengeksplorasi untuk menemukan sesuatu yang baru. Meskipun demikian, mungkin seseorang akan berpikir "oh, hash dihitung dua kali, saya bisa memperbaikinya" dan API akan dibuat, tetapi mungkin lebih sulit bagi pengguna untuk menemukan API tersebut. Saya berharap alat seperti clippy membantu orang-orang itu.
Shepmaster