Lifetime di Rust
Lifetime (masa hidup sebuah referensi) biasanya menjadi titik di mana developer Rust pemula pertama kali benar-benar “mentok”. Kalau kamu merasa begitu, tenang, kamu tidak sendirian. Halaman ini sengaja dibuat santai: tujuannya hanya supaya kamu bisa mengenali apa itu lifetime dan kenapa compiler memintanya, bukan untuk menguasai semua aturannya.
Masalah yang diselesaikan lifetime
Section titled “Masalah yang diselesaikan lifetime”Dari halaman borrowing kamu sudah tahu bahwa referensi (&) membuatmu bisa memakai sebuah nilai tanpa memilikinya. Tapi referensi hanya valid selama nilai yang ditunjuknya masih ada. Rust harus memastikan kamu tidak pernah memegang referensi ke sesuatu yang sudah dibersihkan. Bug semacam itu disebut dangling reference (referensi menggantung), dan merupakan penyebab crash klasik di bahasa seperti C.
Hampir selalu, Rust bisa memeriksanya sendiri dengan melihat scope di kodemu, jadi kamu tidak perlu memikirkannya sama sekali (persis seperti yang kamu lihat di halaman Scope). Tapi kadang, terutama pada fungsi, compiler tidak bisa menyimpulkannya sendiri dan memintamu menuliskannya secara eksplisit. Itulah yang disebut anotasi lifetime.
Fungsi yang butuh bantuan
Section titled “Fungsi yang butuh bantuan”Ini fungsi yang mengembalikan string slice yang lebih panjang di antara dua string slice:
fn longest(a: &str, b: &str) -> &str { if a.len() > b.len() { a } else { b }}Kode ini tidak bisa dikompilasi. Error-nya kira-kira seperti ini:
error[E0106]: missing lifetime specifierArtinya ada penanda lifetime yang kurang. Masalahnya: referensi yang dikembalikan bisa berasal dari a atau dari b, dan Rust tidak bisa tahu yang mana hanya dengan membaca fungsinya. Akibatnya, Rust tidak bisa menjamin referensi yang kamu terima masih valid saat kamu memakainya.
Menambahkan anotasi lifetime
Section titled “Menambahkan anotasi lifetime”Kamu memperbaikinya dengan memberi nama sebuah lifetime (menurut konvensi, huruf kecil seperti 'a) dan memberi tahu Rust bahwa lifetime nilai kembalian terikat pada kedua parameter:
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() > b.len() { a } else { b }}
fn main() { let s1 = String::from("halo"); let s2 = String::from("hai");
let result = longest(&s1, &s2); println!("Terpanjang: {result}");}Output:
Terpanjang: haloBacalah 'a sebagai sebuah label, bukan nilai: ia tidak menentukan durasi yang sebenarnya. Ia hanya memberi tahu Rust bahwa “semua referensi ini harus tetap valid setidaknya sepanjang bagian kode yang sama.” Compiler lalu memeriksa setiap tempat fungsi itu dipanggil untuk memastikan hal itu memang benar.
Kenapa ini penting
Section titled “Kenapa ini penting”Tanpa anotasi ini, compiler benar-benar tidak bisa tahu apakah referensi yang dikembalikan longest mungkin hidup lebih lama daripada salah satu string asalnya. Anotasi lifetime-lah yang membuatnya bisa membuktikan, saat kompilasi dan tanpa biaya runtime sama sekali, bahwa referensi yang kamu terima selalu aman dipakai.
Struct yang menyimpan referensi
Section titled “Struct yang menyimpan referensi”Tempat berikutnya kamu akan bertemu lifetime adalah di struct. Sejauh ini, setiap field struct memiliki datanya sendiri, misalnya sebuah String. Field juga bisa berupa referensi, tapi kalau begitu struct-nya hanya valid selama data yang ditunjuknya masih ada. Rust memintamu menuliskan hal itu dengan sebuah lifetime:
struct Highlight<'a> { text: &'a str, // meminjam dari String yang hidup di tempat lain}
fn main() { let book = String::from("Call me Ishmael. Some years ago..."); let first = Highlight { text: &book[..15] };
println!("Kutipan: {}", first.text); println!("Seluruh buku panjangnya {} byte", book.len());}Output:
Kutipan: Call me IshmaelSeluruh buku panjangnya 34 byteBacalah Highlight<'a> sebagai “sebuah Highlight yang meminjam sesuatu selama lifetime 'a”. Bagian &book[..15] adalah sebuah slice: ia meminjam 15 byte pertama dari book tanpa menyalinnya. Karena first hanya meminjam, book masih bisa dipakai sesudahnya. (Teks bukunya sengaja dibiarkan dalam bahasa Inggris, karena hasil slice dan jumlah byte-nya bergantung pada teks itu.)
Lifetime inilah yang membuat Rust bisa menangkap kesalahan berikut, di mana String-nya dibuang selagi struct masih menunjuk ke dalamnya:
struct Highlight<'a> { text: &'a str,}
fn main() { let highlight; { let book = String::from("Call me Ishmael. Some years ago..."); highlight = Highlight { text: &book[..15] }; } // book dibuang di sini...
println!("{}", highlight.text); // ...tapi highlight masih menunjuk ke dalamnya}error[E0597]: `book` does not live long enough --> src/main.rs:9:40 | 8 | let book = String::from("Call me Ishmael. Some years ago..."); | ---- binding `book` declared here 9 | highlight = Highlight { text: &book[..15] }; | ^^^^ borrowed value does not live long enough10 | } // book dibuang di sini... | - `book` dropped here while still borrowed11 |12 | println!("{}", highlight.text); // ...tapi highlight masih menunjuk ke dalamnya | -------------- borrow later used hereCompiler bilang book tidak hidup cukup lama: ia dibuang di baris 10 selagi masih dipinjam, padahal pinjamannya masih dipakai di baris 12.
Tips praktis: kalau lifetime di struct mulai merepotkan, simpan String yang dimiliki sendiri alih-alih &str. Teksnya memang jadi disalin, tapi struct-nya tidak lagi bergantung pada apa pun, dan lifetime-nya hilang. Banyak programmer Rust berpengalaman memulai dengan cara itu dan baru beralih ke referensi kalau ada alasannya.
Kabar baiknya
Section titled “Kabar baiknya”Kamu akan jauh lebih jarang menulis 'a daripada yang mungkin terkesan dari halaman ini. Banyak kode Rust yang tidak pernah butuh lifetime eksplisit, karena compiler bisa menyimpulkannya di hampir semua situasi sehari-hari. Kamu terutama akan bertemu lifetime saat sebuah fungsi mengembalikan referensi dan compiler tidak bisa menebak sendiri dari mana asalnya. Saat itu terjadi, ingat saja: itu bukan bug, itu compiler yang memintamu memastikan sesuatu yang tidak bisa ia buktikan sendiri.
Latihan
Section titled “Latihan”1. String yang lebih pendek
Section titled “1. String yang lebih pendek”Tulis fungsi shortest yang menerima dua string slice dan mengembalikan yang lebih pendek. Kalau panjangnya sama, kembalikan yang pertama. Panggil dengan "kupu-kupu" dan "semut".
Petunjuk: bentuknya sama dengan longest di atas. Kamu butuh <'a> setelah nama fungsinya, dan 'a di kedua parameter serta di tipe kembaliannya.
Lihat solusi
fn shortest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() <= b.len() { a } else { b }}
fn main() { let big = String::from("kupu-kupu"); let small = String::from("semut");
println!("Terpendek: {}", shortest(&big, &small)); println!("Kalau seri, yang pertama menang: {}", shortest("sapi", "kuda"));}Output:
Terpendek: semutKalau seri, yang pertama menang: sapiSlice yang dikembalikan bisa berasal dari a atau dari b, jadi Rust butuh 'a untuk tahu bahwa hasilnya hanya valid selama kedua input masih valid. Memakai <= alih-alih < membuat a yang dipilih kalau panjangnya seri.
2. Struct yang meminjam
Section titled “2. Struct yang meminjam”Buat struct Quote dengan dua field &str, yaitu text dan author. Buat satu instance di mana text meminjam 20 byte pertama dari String "Fearless concurrency is a goal of Rust", dan author adalah literal "The Rust Book". Cetak sebagai Fearless concurrency (The Rust Book).
Petunjuk: seperti Highlight di atas, struct ini butuh lifetime: struct Quote<'a>, dengan &'a str untuk kedua field-nya. &speech[..20] adalah slice dari 20 byte pertama.
Lihat solusi
struct Quote<'a> { text: &'a str, author: &'a str,}
fn main() { let speech = String::from("Fearless concurrency is a goal of Rust"); let quote = Quote { text: &speech[..20], author: "The Rust Book", };
println!("{} ({})", quote.text, quote.author);}Output:
Fearless concurrency (The Rust Book)quote meminjam dari speech, jadi ia tidak boleh hidup lebih lama darinya. Literal "The Rust Book" tertanam di dalam program dan hidup selama program berjalan, jadi ia jelas bertahan cukup lama untuk 'a. Coba buat speech di dalam blok { } dan cetak kutipannya setelah blok itu berakhir, seperti contoh error di atas, lalu lihat bagaimana compiler menghentikanmu.
Ringkasan
Section titled “Ringkasan”- Lifetime memberi tahu compiler bagaimana referensi-referensi saling berhubungan, supaya ia bisa mencegah dangling reference.
- Kamu hanya perlu menuliskannya kalau compiler tidak bisa menyimpulkannya sendiri, paling sering pada fungsi yang mengembalikan referensi.
'atidak menentukan durasi; ia menghubungkan lifetime beberapa referensi supaya compiler bisa memeriksanya.- Struct yang menyimpan referensi juga butuh lifetime (
struct Highlight<'a>), dan tidak boleh hidup lebih lama dari data yang dipinjamnya. MenyimpanStringyang dimiliki sendiri menghindari hal itu. - Semua ini murni pemeriksaan saat kompilasi; tidak ada biaya tambahan saat runtime.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Masalah apa yang diselesaikan oleh lifetime?
2. Kenapa fn longest(a: &str, b: &str) -> &str gagal dikompilasi tanpa anotasi lifetime?
3. Apakah menambahkan anotasi lifetime menambah biaya saat runtime?
4. Menurut halaman ini, kapan kamu paling sering perlu menulis lifetime secara eksplisit?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- Validating References with Lifetimes — The Rust Book (bahasa Inggris): penjelasan resmi selengkapnya, termasuk lifetime di struct, aturan elision, dan
'static. - Functions — Rust by Example (bahasa Inggris): contoh singkat fungsi dengan anotasi lifetime, dan mana yang ditolak compiler.
- Structs — Rust by Example (bahasa Inggris): lebih banyak struct dan enum yang menyimpan referensi.
- Lifetime elision — The Rust Reference (bahasa Inggris): aturan persis yang dipakai compiler untuk mengisi lifetime untukmu, jadi kamu tahu kapan boleh tidak menuliskannya.