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 konkret | Memenuhi T: 'static | Alasan |
|---|---|---|
i32 | ya | tidak menyimpan reference di dalamnya |
String | ya | memiliki datanya sendiri, tidak menyimpan pinjaman |
&'static str | ya | pinjaman yang tersimpan berumur seumur program |
&String pinjaman lokal | tidak | menyimpan 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:
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:
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:
- Baris 7 pada pesan (
binding 'text' declared here):textadalah pemilikStringdi heap dengan umur biasa — dariletsampai akhirmain. - Baris 8 pada pesan (
store(&text);): kita mengirim&text, sehinggaTdiisi tipe konkret&Stringyang menyimpan pinjaman berumur pendek. BoundT: 'staticmenuntut pinjaman itu berumur seumur program — baris argument requires thattextis borrowed for'staticmenyebut tuntutan itu persis. - Catatan
note: requirement ... introduced heremenunjuk baris 1: kompilator menunjuk asal tuntutan — deklarasi bound pada tanda tanganstore. Pemeriksaan lifetime bound digabungkan dengan pemeriksaan umur data, sehingga penolakannya memakai kode E0597 yang sudah kita kenal, kini dengan tuntutan'staticyang eksplisit. - Baris 9 pada pesan (
`- `text` dropped here while still borrowed`):textdi-drop di akhirmain— 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:
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:
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:
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:
Nilai diterima dan siap disimpan
Nilai diterima dan siap disimpan
Nilai diterima dan siap disimpan
Semua nilai diterimaMari kita bedah kode di atas:
- Baris 6–9 (
fn forward<T>(value: T) where T: 'static,): bound dipindah kewhereclause — bentuk tulisannya lain, maknanya sama denganfn forward<T: 'static>. SyaratT: 'statickini mengalir:forwardhanya menerima tipe yangstoreterima, dan pengecekan itu berlaku di setiap pemanggilan. - Baris 14 (
forward(42);):Tdiisii32— tanpa reference di dalamnya, bound terpenuhi otomatis. - Baris 15 (
forward(String::from("Halo"));):TdiisiString— pemilik data sendiri, bound terpenuhi. Kepemilikan berpindah keforward, lalu kestore, lalu di-drop; tidak ada pinjaman yang lolos. - Baris 16 (
forward("Halo, Rust");):Tdiisi&'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:
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:
Skor akhir: 95Mari kita bedah kode di atas:
- Baris 3 (
struct Report<'a, T: Display + 'a>): dua syarat digabung pada satu bound — kemampuanDisplayuntuk mencetak, dan umurT: 'akarena fieldvaluememinjamTselamaa. Struct ini kini menolak tipe yang tak bisa dicetak maupun tipe yang menyimpan pinjaman lebih pendek daria. - Baris 5 (
value: &'a T): field meminjam nilai bertipeTselama umura; judul di baris 4 meminjamstrdengan umur yang sama. - Baris 8 (
impl<'a, T: Display + 'a> Report<'a, T>): impl block mengulang daftar syarat yang sama agar method di dalamnya boleh memakai kemampuanDisplaydan jaminan umur tersebut. - Baris 16–19 (konstruksi
Reportdimain):Tdiisii32— bisa dicetak, tanpa pinjaman di dalamnya, sehingga kedua syarat lolos tanpa gesekan;adiisi umurscoredi dalammain.
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.