Lewati ke konten

Error Rust E0106: missing lifetime specifier (lifetime tidak disebutkan)

E0106 artinya: kamu menulis sebuah referensi (&) di tipe kembalian fungsi atau di dalam struct, dan Rust tidak bisa tahu referensi itu meminjam dari mana. Ini bukan bug di logikamu. Compiler cuma butuh satu kata tambahan darimu, atau sedikit perubahan rencana. Hampir semua orang bertemu error ini di minggu pertama, dan perbaikannya biasanya cuma satu baris. (Halaman ini bagian dari Penjelasan error compiler Rust.)

error[E0106]: missing lifetime specifier
--> src/main.rs:1:32
|
1 | fn longer(a: &str, b: &str) -> &str {
| ---- ---- ^ 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 `a` or `b`
help: consider introducing a named lifetime parameter
|
1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
| ++++ ++ ++ ++

Compiler bilang tipe kembaliannya berisi nilai pinjaman, tapi signature-nya tidak menyebut apakah nilai itu dipinjam dari a atau dari b. Ia juga langsung menyarankan perbaikannya: tambahkan parameter lifetime bernama 'a.

Ini program yang menghasilkan error tersebut:

fn longer(a: &str, b: &str) -> &str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", longer("apple", "fig"));
}

Referensi adalah sebuah pinjaman (borrowing, peminjaman): ia menunjuk ke data yang dimiliki pihak lain. Setiap kali fungsi mengembalikan referensi, Rust perlu tahu data milik siapa yang ditunjuknya, supaya bisa memastikan data itu tetap hidup selama pemanggil memakai hasilnya.

Lihat tiga tanda di bawah baris 1. Compiler menggarisbawahi a dan b dengan ----, dan tipe kembaliannya dengan ^. Maksudnya: “kamu mengembalikan &str pinjaman, dan itu bisa berasal dari a atau dari b. Yang mana?” Compiler tidak mau menebak.

Bayangkan kartu perpustakaan. Kalau teman memberimu buku dan bilang “ini pinjaman”, kamu pasti ingin tahu dari siapa, supaya tahu kapan buku itu harus dikembalikan. Lifetime adalah label pada pinjaman itu.

Rust menjamin sebuah referensi tidak akan pernah hidup lebih lama dari data yang ditunjuknya (tidak ada “dangling pointer”). Untuk memeriksa itu saat fungsi dipanggil, compiler hanya melihat signature fungsinya, bukan isinya. Jadi signature itu sendiri harus menyebutkan dari mana referensi yang dikembalikan berasal.

Biasanya kamu tidak perlu menulis lifetime sama sekali, karena Rust mengisinya sendiri dengan tiga aturan elision sederhana:

  1. Setiap parameter referensi mendapat lifetime-nya sendiri.
  2. Kalau ada tepat satu parameter referensi, referensi yang dikembalikan meminjam dari parameter itu.
  3. Kalau fungsinya method dengan &self atau &mut self, referensi yang dikembalikan meminjam dari self.

E0106 muncul kalau tidak satu pun aturan ini memberi jawaban: ada dua parameter referensi atau lebih (tanpa self), tidak ada parameter referensi sama sekali, atau ada referensi yang disimpan di struct. Pelajaran Lifetime membahas ini lebih dalam, dan Borrowing menjelaskan referensi itu sendiri.

Perbaikan 1: Tambahkan lifetime bernama ('a)

Section titled “Perbaikan 1: Tambahkan lifetime bernama ('a)”

Pilih ini kalau fungsinya memang mengembalikan bagian dari salah satu input-nya. 'a hanyalah sebuah nama. Menuliskannya di input dan output berarti “hasilnya hidup selama input-input ini hidup”:

// 'a: &str yang dikembalikan meminjam dari a atau b, jadi ia hidup selama keduanya hidup.
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let first = String::from("apple");
let second = String::from("fig");
println!("Kata yang lebih panjang: {}", longer(&first, &second));
}

Output:

Kata yang lebih panjang: apple
▶ Coba sendiri

Kalau hasilnya selalu berasal dari satu parameter saja, pasang 'a hanya di parameter itu: fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str>. Saran compiler memasang 'a di semua tempat. Itu berhasil, tapi lebih ketat dari yang diperlukan.

Perbaikan 2: Kembalikan String milik sendiri

Section titled “Perbaikan 2: Kembalikan String milik sendiri”

Pilih ini kalau fungsinya membuat teks baru, atau kalau kamu memang tidak ingin pemanggil terikat pada sebuah pinjaman. String yang owned (dimiliki sendiri) menjadi milik pemanggil, jadi tidak ada yang perlu dilacak:

fn greeting(name: &str, lang: &str) -> String {
if lang == "id" {
format!("Halo, {name}!")
} else {
format!("Hello, {name}!")
}
}
fn main() {
println!("{}", greeting("Ana", "id"));
println!("{}", greeting("Sam", "en"));
}

Output:

Halo, Ana!
Hello, Sam!
▶ Coba sendiri

Ini memang butuh satu alokasi kecil. Untuk kebanyakan program, itu sama sekali tidak masalah. Jangan merasa bersalah memakainya.

Perbaikan 3: Simpan data owned di struct-mu

Section titled “Perbaikan 3: Simpan data owned di struct-mu”

Field struct seperti title: &str memberi error yang sama:

error[E0106]: missing lifetime specifier
--> src/main.rs:2:12
|
2 | title: &str,
| ^ expected named lifetime parameter

Untuk struct, perbaikan terbaik hampir selalu adalah memiliki datanya sendiri: pakai String alih-alih &str, dan Vec<T> alih-alih &[T]. Dengan begitu struct-nya bisa hidup selama yang kamu mau:

#[derive(Debug)]
struct Book {
title: String, // owned, bukan &str
pages: u32,
}
fn main() {
let book = Book { title: String::from("Dune"), pages: 412 };
println!("{} ({} halaman)", book.title, book.pages);
}

Output:

Dune (412 halaman)
▶ Coba sendiri

Beri struct sebuah lifetime hanya kalau struct itu adalah tampilan sementara ke data yang hidup di tempat lain, misalnya parser yang membaca teks besar. Dalam kasus itu, tulis <'a> di struct dan di field-nya:

struct Excerpt<'a> {
text: &'a str, // meminjam dari novel di bawah
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first = novel.split('.').next().unwrap();
let excerpt = Excerpt { text: first };
println!("Kutipan: {}", excerpt.text);
}

Output:

Kutipan: Call me Ishmael
▶ Coba sendiri

Perbaikan 4: Pakai 'static untuk string literal

Section titled “Perbaikan 4: Pakai 'static untuk string literal”

Kalau fungsinya tidak punya parameter referensi sama sekali, error-nya bilang “there is no value for it to be borrowed from” (tidak ada nilai yang bisa dipinjam):

error[E0106]: missing lifetime specifier
--> src/main.rs:1:22
|
1 | fn default_name() -> &str {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

Kalau kamu hanya mengembalikan string literal seperti "OK", tulis &'static str. Literal sudah tertanam di dalam programmu, jadi ia hidup selama program berjalan:

fn status_text(code: u16) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
_ => "Unknown",
}
}
fn main() {
println!("200 artinya {}", status_text(200));
println!("404 artinya {}", status_text(404));
}

Output:

200 artinya OK
404 artinya Not Found
▶ Coba sendiri

Pakai 'static hanya untuk literal dan konstanta sungguhan. Kalau teksnya dibuat saat program berjalan (dengan format!, atau dibaca dari file), 'static tidak akan menolong: kembalikan String (Perbaikan 2).

  • Fungsi pembantu dengan dua input, seperti fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>. Perbaikan: ikat output-nya hanya ke map dengan 'a (Perbaikan 1).
  • Struct yang menyimpan teks, seperti User { name: &str } yang dibuat dari input hasil parsing. Perbaikan: pakai field String (Perbaikan 3).
  • Fungsi yang mengembalikan pesan tetap, seperti fn error_label() -> &str. Perbaikan: &'static str (Perbaikan 4).

Kabar baiknya ada di method: fn name(&self) -> &str sama sekali tidak butuh lifetime, berkat aturan nomor 3.

Kuis singkat

1. Kenapa fn first_word(s: &str) -> &str bisa dikompilasi tanpa lifetime apa pun?

2. Struct Config { path: &str } milikmu memberi E0106. Apa perbaikan yang biasanya paling baik?

3. Sebuah fungsi membuat pesan dengan format! lalu mengembalikannya. Tipe kembalian apa yang seharusnya dipakai?

Skor: 0 / 3