Struct Excerpt<'a> dari Chapter sebelumnya kini milik kita, dan setiap struct yang layak biasanya disertai impl block berisi method. Kabar baiknya sudah kita baca sebagai aturan ketiga lifetime elision: method yang mengembalikan bagian dari self tidak memerlukan anotasi apa pun. Kabar yang perlu digali lebih dalam: impl block untuk struct berparameter lifetime punya sintaks deklarasinya sendiri, method boleh punya lifetime parameter pribadi yang berbeda peran dari milik struct, dan associated function tanpa self berdiri di luar jangkauan aturan ketiga. Chapter ini menempuh ketiganya secara berurutan dengan struct Excerpt sebagai peraga.

impl untuk Struct Berparameter Lifetime #

Membuat method untuk Excerpt<'a> dimulai dari deklarasi impl<'a> Excerpt<'a> — lifetime parameter diperkenalkan di deklarasi impl block, lalu dipakai pada tanda tangan setiap method yang membutuhkannya. Dua method pertama kita sengaja memakai elision penuh: show hanya membaca tanpa mengembalikan reference, sedangkan announce mengembalikan potongan milik struct lewat self:

rust
struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    fn show(&self) {
        println!("Potongan saat ini: {}", self.part);
    }

    fn announce(&self, message: &str) -> &str {
        println!("Pengumuman: {}", message);
        self.part
    }
}

fn main() {
    let novel = String::from("Belajar Rust itu menyenangkan");
    let excerpt = Excerpt {
        part: &novel[..7],
    };

    excerpt.show();

    let part = excerpt.announce("Rilis Rust baru sudah dekat");
    println!("Potongan: {}", part);
}

Output yang Dihasilkan:

text
Potongan saat ini: Belajar
Pengumuman: Rilis Rust baru sudah dekat
Potongan: Belajar

Mari kita bedah kode di atas:

  1. Baris 5 (impl<'a> Excerpt<'a>): lifetime parameter a dideklarasikan milik impl block. Seluruh method di dalamnya boleh memakai a pada tanda tangannya — meski dua method pertama kita belum membutuhkannya.
  2. Baris 6 (fn show(&self)): tanda tangan tanpa reference pada nilai kembalian tidak memicu kebutuhan anotasi. Aturan ketiga pun tidak berperan; method seperti ini bebas urusan umur.
  3. Baris 10 (fn announce(&self, message: &str) -> &str): dua input lifetime hadir — pinjaman self dan pinjaman message — lalu aturan ketiga elision mengisi output lifetime dengan umur self. Kontraknya tertulis otomatis: hasil announce hidup tidak lebih lama dari pinjaman atas excerpt.
  4. Baris 12 (self.part): nilai kembalian meminjam field melalui self, konsisten dengan kontrak tersebut. Pemanggil yang menerima hasilnya tidak boleh menyimpannya melampaui umur pinjaman &self — dan karena excerpt sendiri terikat pada umur novel, seluruh rantai umur tetap aman.
  5. Baris 17–20 (let novel = ... hingga konstruksi Excerpt): pola yang sama dengan Chapter sebelumnya — a diisi umur pinjaman &novel[..7] saat konstruksi.
  6. Baris 24–25 (let part = excerpt.announce(...) lalu println!): hasil part dipakai segera setelah pemanggilan; pinjaman &self berakhir pada pemakaian terakhir part, jauh sebelum novel mati di akhir main.

Ketika Elision Memilih Kebalikan dari Maksud Kita #

Aturan ketiga mengisi output lifetime dengan umur self — pilihan yang tepat untuk method seperti announce. Namun method yang mengembalikan parameter alih-alih bagian dirinya punya maksud sebaliknya. Misalkan pick_part membandingkan dua teks dari pemanggil dan mengembalikan yang lebih panjang; teks sumbernya bukan milik struct, melainkan milik pemanggil method. Versi tanpa anotasi berikut tidak dapat dikompilasi:

rust
struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    fn pick_part(&self, left: &str, right: &str) -> &str {
        if left.len() >= right.len() {
            left
        } else {
            right
        }
    }
}

fn main() {
    let novel = String::from("Belajar Rust itu menyenangkan");
    let excerpt = Excerpt {
        part: &novel[..7],
    };

    let chosen = excerpt.pick_part("Semangat", "Rust");
    println!("Pilihan: {}", chosen);
}

Pesan error kompilator:

text
error: lifetime may not live long enough
 --> main.rs:8:13
  |
6 |     fn pick_part(&self, left: &str, right: &str) -> &str {
  |                  -            - let's call the lifetime of this reference `'1`
  |                  |
  |                  let's call the lifetime of this reference `'2`
7 |         if left.len() >= right.len() {
8 |             left
  |             ^^^^ method was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`
  |
help: consider introducing a named lifetime parameter and update trait if needed
  |
6 |     fn pick_part<'b>(&self, left: &'b str, right: &str) -> &'b str {
  |                 ++++               ++                       ++

error: lifetime may not live long enough
  --> main.rs:10:13
   |
 6 |     fn pick_part(&self, left: &str, right: &str) -> &str {
   |                  -                         - let's call the lifetime of this reference `'3`
   |                  |
   |                  let's call the lifetime of this reference `'2`
...
10 |             right
   |             ^^^^^ method was supposed to return data with lifetime `'2` but it is returning data with lifetime `'3`
   |
help: consider introducing a named lifetime parameter and update trait if needed
  |
 6 |     fn pick_part<'b>(&self, left: &str, right: &'b str) -> &'b str {
   |                 ++++                            ++          ++

error: aborting due to 2 previous errors

Pesan ini memperlihatkan aturan ketiga bekerja dari sisi yang kalah. Untuk memudahkan pembacaan, kompilator memberi label sementara: '2 adalah umur pinjaman &self, '1 umur left, '3 umur right. Elision telah menetapkan output lifetime mengikuti '2 — umur self — karena method memiliki &self. Tubuh method kemudian mengembalikan left berumur '1 dan right berumur '3; pesan “method was supposed to return data with lifetime '2” menegaskan konfliknya: tidak ada jaminan left atau right hidup selama self, sehingga kontrak otomatis aturan ketiga tidak bisa dipenuhi. Kompilator pun menyodorkan arah perbaikan yang benar — deklarasikan lifetime parameter pada method itu sendiri.

Perbaikannya adalah lifetime parameter <'b> milik method, yang mengikat kedua parameter teks dan nilai kembalian menjadi satu keluarga umur, terlepas dari umur self:

rust
struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    fn pick_part<'b>(&self, left: &'b str, right: &'b str) -> &'b str {
        if left.len() >= right.len() {
            left
        } else {
            right
        }
    }
}

fn main() {
    let novel = String::from("Belajar Rust itu menyenangkan");
    let excerpt = Excerpt {
        part: &novel[..7],
    };

    let chosen = excerpt.pick_part("Semangat", "Rust");
    println!("Potongan: {}", excerpt.part);
    println!("Pilihan: {}", chosen);
}

Output yang Dihasilkan:

text
Potongan: Belajar
Pilihan: Semangat

Mari kita bedah perubahan kuncinya:

  1. Baris 6 (fn pick_part<'b>(&self, left: &'b str, right: &'b str) -> &'b str): lifetime b dideklarasikan setelah nama method — milik method ini saja, terpisah dari a milik impl block. Output kini terikat pada umur left dan right, bukan umur self; tubuh fungsi konsisten dengan kontrak baru.
  2. Baris 21 (let chosen = excerpt.pick_part("Semangat", "Rust");): kedua argumen berupa string literal berumur 'static, sehingga b terisi umur yang panjang. chosen tidak meminjam excerpt sama sekali — ia meminjam data milik pemanggil, dan boleh hidup sesudah excerpt selesai dipakai.
  3. Baris 22–23 (println! keduanya): excerpt.part dan chosen dicetak berdampingan tanpa konflik — keduanya pinjaman yang berbeda sumber dan berbeda kontrak, keduanya masih valid pada titik ini.

Perbedaan peran a dan b inilah pelajaran penting impl block: a milik struct mengikat field pada data sumber struct, sedangkan b milik method mengikat parameter pada nilai kembalian method. Keduanya hidup berdampingan pada tanda tangan yang sama tanpa saling mencampur.

Associated Function Tanpa self #

Associated function — fungsi di impl block tanpa parameter self, yang dipanggil lewat sintaks Excerpt::from_text(...) — tidak mendapat perlakuan khusus aturan ketiga karena tidak meminjam instance. Fungsi pembangun berikut menerima potongan teks dan membungkusnya menjadi Excerpt:

rust
struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    fn from_text(text: &'a str) -> Excerpt<'a> {
        Excerpt { part: text }
    }
}

fn main() {
    let novel = String::from("Belajar Rust itu menyenangkan");
    let excerpt = Excerpt::from_text(&novel[..7]);
    println!("Potongan: {}", excerpt.part);
}

Output yang Dihasilkan:

text
Potongan: Belajar

Mari kita bedah kode di atas:

  1. Baris 6 (fn from_text(text: &'a str) -> Excerpt<'a>): parameter dan nilai kembalian sama-sama memakai a milik impl block — bukan lifetime baru milik fungsi. Hubungan yang dinyatakan sederhana namun kuat: instance yang dihasilkan meminjam dari teks yang dikirim, sehingga umur instance tidak boleh melampaui umur teks tersebut.
  2. Baris 7 (Excerpt { part: text }): field part menerima pinjaman text apa adanya — tidak ada penyalinan, hanya pemindahan penunjuk dan panjang.
  3. Baris 13 (let excerpt = Excerpt::from_text(&novel[..7]);): dipanggil lewat path Excerpt::from_text, tanpa instance. Potongan &novel[..7] berumur selama novel hidup, sehingga excerpt terikat pada umur yang sama.

Ringkasan #

Impl block untuk struct berparameter lifetime ditulis impl<'a> NamaStruct<'a>, dan setiap method bebas memakai a atau mendeklarasikan lifetime pribadi <'b> sesuai kebutuhan. Aturan ketiga elision mengurus method yang mengembalikan bagian dari self tanpa anotasi; ketika method justru mengembalikan parameter pemanggil, aturan yang sama menghasilkan kontrak yang keliru dan penolakan “lifetime may not live long enough”, yang diselesaikan dengan lifetime parameter milik method. Associated function tanpa self berdiri di luar aturan ketiga dan paling lazim menuliskan &'a secara eksplisit agar hubungan instance dengan data masukan terbaca langsung. Satu wilayah masih tersisa di peta lifetime: lifetime bernama yang berumur sepanjang program — 'static.