Dua Chapter sebelumnya seakan menjanjikan masa depan yang melelahkan: kalau setiap fungsi dan struct ber-reference harus ditulisi <'a>, kode Rust sehari-hari akan penuh anotasi. Kenyataannya justru sebaliknya — fungsi first_word di section Slice dan Referensi berjalan tanpa satu pun anotasi, padahal tanda tangannya &str masuk dan &str keluar. Rahasianya adalah lifetime elision: kompilator menuliskan anotasi untuk kita pada pola-pola tanda tangan yang paling umum, mengikuti tiga aturan yang pasti dan dapat kita hafal. Chapter ini membedah ketiga aturan tersebut satu per satu dengan contoh nyata, lalu menutupnya dengan pola yang tidak tertutup oleh aturan mana pun — dan karenanya tetap menuntut anotasi eksplisit.
Terminologi Input dan Output Lifetime #
Sebelum aturan, kita perlu dua istilah. Input lifetime adalah umur pada parameter bertipe reference — misalnya umur dari text: &str pada sebuah tanda tangan. Output lifetime adalah umur pada tipe kembalian bertipe reference — misalnya umur dari &str pada nilai kembalian first_word. Parameter non-reference seperti usize atau bool tidak membawa umur apa pun dan tidak ikut dihitung. Elision hanya berurusan dengan posisi-posisi tersebut pada tanda tangan; tubuh fungsi tidak dibaca sama sekali. Karena itu hasilnya selalu pasti — elision bukan tebakan cerdas, melainkan tiga aturan penulisan otomatis yang deterministik.
Aturan 1: Setiap Parameter Reference Mendapat Lifetime Sendiri #
Aturan pertama menyatakan: setiap parameter reference pada tanda tangan diberi input lifetime miliknya sendiri, terpisah dari parameter lain. Dua parameter &str tidak dipaksa berumur sama — masing-masing berdiri sebagai pinjaman mandiri. Program berikut memperlihatkan aturan ini bekerja pada fungsi print_both, sekaligus aturan kedua pada fungsi first_word yang sudah kita kenal:
fn print_both(left: &str, right: &str) {
println!("{} dan {}", left, right);
}
fn first_word(text: &str) -> &str {
let bytes = text.as_bytes();
let mut index = 0;
while index < bytes.len() {
if bytes[index] == b' ' {
return &text[..index];
}
index += 1;
}
text
}
fn main() {
let sentence = String::from("Belajar Rust itu menyenangkan");
{
let note = String::from("kata kunci");
print_both(&sentence, ¬e);
}
let word = first_word(&sentence);
println!("Kata pertama: {}", word);
}Output yang Dihasilkan:
Belajar Rust itu menyenangkan dan kata kunci
Kata pertama: BelajarMari kita bedah bagian yang relevan dengan aturan pertama:
- Baris 1 (
fn print_both(left: &str, right: &str)): elision memberileftdanrightdua input lifetime yang berbeda. Tidak ada relasi umur yang dipaksakan di antara keduanya — fungsi hanya membaca kedua teks lalu selesai. - Baris 20 (
let sentence = String::from("Belajar Rust itu menyenangkan");): pemilik teks panjang hidup sampai akhirmain— sumber pinjaman yang umurnya paling panjang di program ini. - Baris 23–24 (
let note = ...laluprint_both(&sentence, ¬e)di dalam blok): kedua argumen berasal dari scope yang berbeda —sentencehidup sampai akhirmain,notehanya hidup di blok dalam. Pemanggilan tetap sah karena pinjaman kedua parameter hanya dibutuhkan selama tubuhprint_bothberjalan; begitu panggilan usai, kedua pinjaman selesai, dannoteboleh mati saat blok ditutup tanpa mengganggu apa pun.
Aturan 2: Satu Input Lifetime Menjadi Umur Semua Output #
Aturan kedua menyatakan: jika tanda tangan memiliki tepat satu input lifetime, umur tersebut diberikan ke seluruh output lifetime. Fungsi first_word pada program di atas adalah contoh kanonisnya: parameter text adalah satu-satunya parameter reference, sehingga nilai kembalian &str otomatis diikat pada umur text. Kontrak “potongan yang dikembalikan meminjam dari parameter masukan” tertulis sendiri oleh kompilator tanpa satu karakter <'a> dari kita. Mari kita telusuri bagian first_word pada program yang sama:
- Baris 5 (
fn first_word(text: &str) -> &str): satu input lifetime diterapkan pada output — bentuk elision darifn first_word<'a>(text: &'a str) -> &'a str. Kini jelas mengapa fungsi ini lolos tanpa anotasi selama ini. - Baris 11 (
return &text[..index];) dan baris 16 (text): kedua jalur kembalian meminjam dari parameter yang sama, konsisten dengan kontrak yang ditulis elision. - Baris 27 (
let word = first_word(&sentence);): penyetaraan&Stringmenjadi&strdari section Slice terjadi seperti biasa; pinjamanwordterikat pada umursentencedan selesai pada pemakaian terakhirnya di baris 28.
Aturan 3: Umur self Menjadi Umur Output pada Method #
Aturan ketiga berlaku khusus method: jika tanda tangan method menerima &self atau &mut self di antara parameternya, umur pinjaman self yang diberikan ke seluruh output lifetime. Method berikut mengembalikan awalan tujuh byte dari field text milik struct — tanpa anotasi apa pun:
struct Sentence {
text: String,
}
impl Sentence {
fn head(&self) -> &str {
&self.text[..7]
}
}
fn main() {
let sentence = Sentence {
text: String::from("Belajar Rust itu menyenangkan"),
};
println!("Awalan: {}", sentence.head());
}Output yang Dihasilkan:
Awalan: BelajarMari kita bedah kode di atas:
- Baris 6 (
fn head(&self) -> &str): kehadiran&selfmemicu aturan ketiga — output lifetime diisi umur pinjamanself, bukan umur fieldtextitu sendiri. Kontraknya: hasilheadtidak hidup lebih lama dari pinjaman atassentence. - Baris 7 (
&self.text[..7]): nilai kembalian meminjam data milik struct melaluiself. Karenaselfmenjamin struct masih hidup, datanya juga masih hidup — janji terpenuhi. - Baris 16 (
println!("Awalan: {}", sentence.head());): hasil pemanggilan langsung dipakai pada baris yang sama; pinjaman lahir dan berakhir dalam satu ekspresi.
Inilah alasan mayoritas method di Rust tidak memuat anotasi: aturan ketiga menutup pola “mengembalikan bagian dari diri sendiri” yang paling sering dibutuhkan. Chapter Lifetime di Method akan menggali impl block lebih dalam, termasuk saat method memang harus menulis anotasinya sendiri.
Ketika Tidak Ada Aturan yang Berlaku #
Ketiga aturan menutup pola umum, tetapi bukan semua pola. Fungsi pick_larger menerima dua slice angka dan mengembalikan salah satunya — dua input lifetime dari aturan pertama, output yang tidak bisa diisi oleh aturan kedua (input lebih dari satu) maupun aturan ketiga (bukan method, tidak ada self). Kode berikut tidak dapat dikompilasi:
fn pick_larger(left: &[i32], right: &[i32]) -> &[i32] {
if left.len() >= right.len() {
left
} else {
right
}
}
fn main() {
let quiz_scores = [80, 90, 70];
let task_scores = vec![95, 85];
let result = pick_larger(&quiz_scores, &task_scores);
println!("Elemen terpilih: {:?}", result);
}Pesan error kompilator:
error[E0106]: missing lifetime specifier
--> main.rs:1:48
|
1 | fn pick_larger(left: &[i32], right: &[i32]) -> &[i32] {
| ------ ------ ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `left` or `right`
help: consider introducing a named lifetime parameter
|
1 | fn pick_larger<'a>(left: &'a [i32], right: &'a [i32]) -> &'a [i32] {
| ++++ ++ ++ ++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0106`.Sama seperti first_longest di Chapter Anotasi Lifetime di Fungsi, kompilator menolak menebak dan meminta anotasi eksplisit. Perbaikannya pun satu keluarga — satu lifetime bersama untuk kedua input dan output:
fn pick_larger<'a>(left: &'a [i32], right: &'a [i32]) -> &'a [i32] {
if left.len() >= right.len() {
left
} else {
right
}
}
fn main() {
let quiz_scores = [80, 90, 70];
let task_scores = vec![95, 85];
let result = pick_larger(&quiz_scores, &task_scores);
println!("Elemen terpilih: {:?}", result);
}Output yang Dihasilkan:
Elemen terpilih: [80, 90, 70]Pada versi beranotasi, left, right, dan nilai kembalian berbagi a, sehingga di titik pemanggilan result terikat pada irisan umur kedua koleksi sumber — aturan yang sama dengan first_longest, kini dalam wujud slice angka. Elision dan anotasi manual pada akhirnya menghasilkan kontrak yang identik; bedanya hanya pada siapa yang menuliskannya.
Ringkasan #
Lifetime elision membuat mayoritas tanda tangan Rust bersih dari <'a> lewat tiga aturan pasti: setiap parameter reference mendapat input lifetime sendiri (aturan 1), tanda tangan dengan tepat satu input lifetime memberikan umur tersebut ke seluruh output (aturan 2), dan method dengan &self memberikan umur self ke seluruh output (aturan 3). Pola dua parameter tanpa self seperti pick_larger tidak tertutup ketiganya, ditolak sebagai E0106, dan diselesaikan dengan anotasi <'a> bersama. Dengan peta aturan ini, kita siap masuk ke wilayah tempat aturan ketiga paling sering bekerja: method pada struct yang menyimpan pinjaman.