Bagian terakhir dari peta lifetime menyatukan dua dunia yang sudah kita kuasai: lifetime dan generics. Kita terbiasa menulis trait bound seperti T: Display untuk menyatakan syarat kemampuan tipe; tanda kurung siku yang sama juga bisa memuat syarat umur — lifetime bound T: 'a — yang menyatakan “semua pinjaman yang tersimpan di dalam T hidup sekurang-kurangnya selama a”. Bound ini paling sering kita temui dalam wujud T: 'static pada tanda tangan library yang menyimpan nilai melebihi umur pemanggilnya. Chapter ini mengartikan bound tersebut, memicu dua penolakan asli kompilator, lalu menggabungkan lifetime bound dengan trait bound pada satu struct yang berjalan penuh.

Makna dari T: ‘a #

Bound T: 'a dibaca: tipe T outlive lifetime a. Bunyi resminya sederhana, tetapi maknanya sering terbalik dipahami — yang diperiksa bukan umur variabel bertipe T, melainkan pinjaman-peminjaman yang mungkin tersimpan di dalam T. i32 dan String tidak menyimpan reference apa pun di dalamnya, sehingga keduanya otomatis memenuhi T: 'a untuk a apa pun, termasuk 'static. Sebaliknya, &String adalah tipe yang isinya satu pinjaman; memenuhi T: 'static hanya jika pinjaman itu sendiri berumur 'static.

Tipe konkretMemenuhi T: 'staticAlasan
i32yatidak menyimpan reference di dalamnya
Stringyamemiliki datanya sendiri, tidak menyimpan pinjaman
&'static stryapinjaman yang tersimpan berumur seumur program
&String pinjaman lokaltidakmenyimpan pinjaman yang mati lebih dulu

Tabel di atas merangkum pemeriksaan yang dilakukan borrow checker setiap kali bound T: 'static harus diverifikasi. Mari kita lihat pemeriksaan itu gagal dengan cara yang paling membumi.

Kasus Konkret: Bound ‘static di Titik Pemanggilan #

Bayangkan fungsi penampung nilai gaya registry: nilai yang masuk akan disimpan jauh melampaui scope pemanggilnya — konfigurasi aplikasi, handler jangka panjang, data rujukan global. Kontrak semacam itu dinyatakan dengan bound T: 'static. Fungsi mainan kita di bawah tidak benar-benar menyimpan selamanya — nilai tetap di-drop saat fungsi selesai — tetapi bound-nya sudah cukup untuk memicu pemeriksaan yang ingin kita amati. Kode berikut tidak dapat dikompilasi:

rust
fn store<T: 'static>(value: T) {
    let _keep = value;
    println!("Nilai diterima dan siap disimpan");
}

fn main() {
    let text = String::from("Halo");
    store(&text);
}

Pesan error kompilator:

text
error[E0597]: `text` does not live long enough
 --> main.rs:8:11
  |
7 |     let text = String::from("Halo");
  |         ---- binding `text` declared here
8 |     store(&text);
  |     ------^^^^^-
  |     |     |
  |     |     borrowed value does not live long enough
  |     argument requires that `text` is borrowed for `'static`
9 | }
  | - `text` dropped here while still borrowed
  |
note: requirement that the value outlives `'static` introduced here
 --> main.rs:1:13
  |
1 | fn store<T: 'static>(value: T) {
  |             ^^^^^^^

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0597`.

Mari kita bedah pesan tersebut:

  1. Baris 7 pada pesan (binding 'text' declared here): text adalah pemilik String di heap dengan umur biasa — dari let sampai akhir main.
  2. Baris 8 pada pesan (store(&text);): kita mengirim &text, sehingga T diisi tipe konkret &String yang menyimpan pinjaman berumur pendek. Bound T: 'static menuntut pinjaman itu berumur seumur program — baris argument requires that text is borrowed for 'static menyebut tuntutan itu persis.
  3. Catatan note: requirement ... introduced here menunjuk baris 1: kompilator menunjuk asal tuntutan — deklarasi bound pada tanda tangan store. Pemeriksaan lifetime bound digabungkan dengan pemeriksaan umur data, sehingga penolakannya memakai kode E0597 yang sudah kita kenal, kini dengan tuntutan 'static yang eksplisit.
  4. Baris 9 pada pesan (`- `text` dropped here while still borrowed`): text di-drop di akhir main — bukti konkrit bahwa pinjaman ini tidak berumur 'static, sehingga kontrak bound tidak terpenuhi.

Perbaikan di sisi pemanggil ada dua: kirim kepemilikan penuh dengan store(text) — String memenuhi T: 'static — atau kirim data yang memang seumur proses seperti literal store("Halo").

Versi Generik: E0310 dan Perbaikannya #

Kasus di atas memakai tipe konkret &String. Kesalahan yang lebih menarik terjadi ketika pelanggar justru kode generik: fungsi forward menerima T apa pun lalu meneruskannya ke store. Karena store menuntut T: 'static, fungsi forward wajib menyatakan tuntutan yang sama — tanpa itu, kompilator menolak mendeklarasikannya. Kode berikut tidak dapat dikompilasi:

rust
fn store<T: 'static>(value: T) {
    let _keep = value;
    println!("Nilai diterima dan siap disimpan");
}

fn forward<T>(value: T) {
    store(value);
}

fn main() {
    forward(42);
}

Pesan error kompilator:

text
error[E0310]: the parameter type `T` may not live long enough
 --> main.rs:7:5
  |
7 |     store(value);
  |     ^^^^^^^^^^^^
  |     |
  |     the parameter type `T` must be valid for the static lifetime...
  |     ...so that the type `T` will meet its required lifetime bounds
  |
help: consider adding an explicit lifetime bound
  |
6 | fn forward<T: 'static>(value: T) {
  |             +++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0310`.

Pesan E0310 menegur deklarasi fungsi, bukan pemanggilan: parameter tipe T belum dijamin berumur cukup. Sebuah pemanggil forward(&text) di masa depan bisa saja mengirim pinjaman pendek, dan forward tidak punya hak menjanjikan bahwa T memenuhi T: 'static jika deklarasinya tidak menyatakannya. Perbaikannya persis seperti usulan kompilator — tambahkan bound, atau nyatakan lewat where clause agar daftar syarat mudah dibaca. Berikut versi lengkap yang sah:

rust
fn store<T: 'static>(value: T) {
    let _keep = value;
    println!("Nilai diterima dan siap disimpan");
}

fn forward<T>(value: T)
where
    T: 'static,
{
    store(value);
}

fn main() {
    forward(42);
    forward(String::from("Halo"));
    forward("Halo, Rust");
    println!("Semua nilai diterima");
}

Output yang Dihasilkan:

text
Nilai diterima dan siap disimpan
Nilai diterima dan siap disimpan
Nilai diterima dan siap disimpan
Semua nilai diterima

Mari kita bedah kode di atas:

  1. Baris 6–9 (fn forward<T>(value: T) where T: 'static,): bound dipindah ke where clause — bentuk tulisannya lain, maknanya sama dengan fn forward<T: 'static>. Syarat T: 'static kini mengalir: forward hanya menerima tipe yang store terima, dan pengecekan itu berlaku di setiap pemanggilan.
  2. Baris 14 (forward(42);): T diisi i32 — tanpa reference di dalamnya, bound terpenuhi otomatis.
  3. Baris 15 (forward(String::from("Halo"));): T diisi String — pemilik data sendiri, bound terpenuhi. Kepemilikan berpindah ke forward, lalu ke store, lalu di-drop; tidak ada pinjaman yang lolos.
  4. Baris 16 (forward("Halo, Rust");): T diisi &'static str — pinjaman seumur program, bound terpenuhi. Ketiga pola ini menjadi pedoman cepat: angka, data milik, literal.

Menggabungkan Lifetime Bound dengan Trait Bound #

Dunia nyata jarang hanya menuntut umur. Bound lifetime dan trait bound ditulis berdampingan dengan tanda +, dibaca sebagai daftar syarat yang harus dipenuhi tipe tersebut sekaligus: T: Display + 'a berarti T mengimplementasikan trait Display dan seluruh pinjaman di dalamnya berumur sekurang-kurangnya a. Struct berikut menyimpan judul pinjaman bersama nilai generic yang harus bisa dicetak:

rust
use std::fmt::Display;

struct Report<'a, T: Display + 'a> {
    title: &'a str,
    value: &'a T,
}

impl<'a, T: Display + 'a> Report<'a, T> {
    fn print(&self) {
        println!("{}: {}", self.title, self.value);
    }
}

fn main() {
    let score = 95;
    let report = Report {
        title: "Skor akhir",
        value: &score,
    };

    report.print();
}

Output yang Dihasilkan:

text
Skor akhir: 95

Mari kita bedah kode di atas:

  1. Baris 3 (struct Report<'a, T: Display + 'a>): dua syarat digabung pada satu bound — kemampuan Display untuk mencetak, dan umur T: 'a karena field value meminjam T selama a. Struct ini kini menolak tipe yang tak bisa dicetak maupun tipe yang menyimpan pinjaman lebih pendek dari a.
  2. Baris 5 (value: &'a T): field meminjam nilai bertipe T selama umur a; judul di baris 4 meminjam str dengan umur yang sama.
  3. Baris 8 (impl<'a, T: Display + 'a> Report<'a, T>): impl block mengulang daftar syarat yang sama agar method di dalamnya boleh memakai kemampuan Display dan jaminan umur tersebut.
  4. Baris 16–19 (konstruksi Report di main): T diisi i32 — bisa dicetak, tanpa pinjaman di dalamnya, sehingga kedua syarat lolos tanpa gesekan; a diisi umur score di dalam main.

Ringkasan #

Lifetime bound T: 'a menyatakan bahwa tipe T outlive a: semua pinjaman yang tersimpan di dalamnya hidup sekurang-kurangnya selama a, sementara tipe tanpa pinjaman seperti i32 dan String memenuhinya untuk a apa pun. Bound T: 'static pada fungsi penyimpan ditolak di titik pemanggilan lewat E0597 dengan tuntutan pinjaman 'static, dan kode generik yang meneruskan nilai tanpa mendeklarasikan bound ditolak sebagai E0310 lengkap dengan usulan perbaikan. Bound lifetime berdiri berdampingan dengan trait bound dalam satu daftar T: Display + 'a maupun where clause, memberi kendali presisi atas kemampuan sekaligus umur tipe generik.

Ringkasan Section Lifetime #

Perjalanan section ini utuh sekarang. Kita mulai dari masalah nyata referensi menggantung dan dua garis waktu yang dibandingkan borrow checker, lalu mempelajari bahasa anotasi <'a> untuk mendeskripsikan hubungan umur antar reference pada fungsi dan field struct — dengan satu prinsip yang tak pernah berubah: anotasi mendeskripsikan, tidak mengubah umur. Lifetime elision membuktikan bahwa mayoritas tanda tangan tak perlu ditulis manual melalui tiga aturan pastinya, method dan impl block menempatkan aturan itu bekerja dalam kebiasaan sehari-hari, 'static memberi nama pada jaminan seumur program beserta tiga kesalahpahaman klasiknya, dan lifetime bound menutup dengan kendali presisi di dunia generics. Dengan bekal ini, tanda tangan library Rust yang tampak rumit — penuh <'a>, 'static, dan T: 'a — kini dapat kita baca sebagai kontrak umur yang jernih.