Struct generik dari chapter sebelumnya masih berupa wadah hening: Locker<T> bisa menyimpan apa pun, tetapi belum tahu melakukan apa pun. Perilaku tinggal di blok impl — pertanyaannya, bagaimana menulis impl untuk struct yang tipenya belum diputuskan? Jawabannya memuat satu aturan yang sering menjegal pemula: blok impl harus mendeklarasikan ulang type parameternya sendiri. Chapter ini membahas tiga bentuk sekaligus — impl yang melayani semua instansiasi, impl yang hanya melayani satu instansiasi pilihan, dan method yang membawa type parameter tambahan miliknya sendiri.

impl yang Melayani Semua Instansiasi #

Kita lengkapi Locker<T> dengan dua method dasar: membaca isi dan mengganti isi:

rust
struct Locker<T> {
    content: T,
}

impl<T> Locker<T> {
    fn reveal(&self) -> &T {
        &self.content
    }

    fn replace(&mut self, new_content: T) {
        self.content = new_content;
    }
}

fn main() {
    let mut code_locker = Locker { content: 4582 };

    println!("Kode awal: {}", code_locker.reveal());
    code_locker.replace(9103);
    println!("Kode baru: {}", code_locker.reveal());

    let name_locker = Locker { content: String::from("Kunci cadangan") };
    println!("Isi loker nama: {}", name_locker.reveal());
}

Output yang Dihasilkan:

text
Kode awal: 4582
Kode baru: 9103
Isi loker nama: Kunci cadangan

Mari kita bedah kode di atas baris per baris:

  1. Baris 5 (impl<T> Locker<T> {): blok impl membuka deklarasi <T>-nya sendiri. T pada baris ini adalah parameter baru yang menggantikan seluruh rentang instansiasi — blok ini berlaku untuk Locker<i32>, Locker<String>, dan instansiasi lainnya sekaligus. Penulisan impl<T> adalah pernyataan “untuk setiap T”.
  2. Baris 6 (fn reveal(&self) -> &T {): method pembaca dengan penerima &self — pola pinjaman yang sudah kita kuasai. Nilai kembali &T berarti “reference ke konten bertipe apa pun instansiasi ini”.
  3. Baris 10 (fn replace(&mut self, new_content: T)): method pengubah. Parameter new_content bertipe T — tipe konten yang tepat untuk loker ini — dan kepemilikannya berpindah dari pemanggil ke field content saat baris 11 menugaskannya.
  4. Baris 17-20 (pemakaian pada code_locker): instansiasi Locker<i32>; replace(9103) mengirim i32 yang cocok dengan T = i32. Binding mut wajib karena replace meminta &mut self.
  5. Baris 22-23 (pemakaian pada name_locker): instansiasi Locker<String> memanggil method yang sama — satu blok impl melayani dua tipe tanpa duplikasi.

Kesalahan paling umum saat pertama kali menulis blok ini adalah lupa mendeklarasikan ulang parameternya:

rust
struct Locker<T> {
    content: T,
}

impl Locker<T> {
    fn reveal(&self) -> &T {
        &self.content
    }
}

fn main() {
    let locker = Locker { content: String::from("Kunci cadangan") };
    println!("{}", locker.reveal());
}

Pesan error kompilator:

text
error[E0425]: cannot find type `T` in this scope
 --> main.rs:5:13
  |
5 | impl Locker<T> {
  |             ^ not found in this scope
  |
help: you might be missing a type parameter
  |
5 | impl<T> Locker<T> {
  |     +++

error[E0425]: cannot find type `T` in this scope
 --> main.rs:6:26
  |
6 |     fn reveal(&self) -> &T {
  |                          ^ not found in this scope

error: aborting due to 2 previous errors

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

Pesan E0425 menegaskan bahwa T pada blok impl adalah variabel tersendiri: deklarasi <T> pada struct sudah “habis pakai” setelah definisi struct, dan blok impl harus mendeklarasikan parameternya sendiri agar T masuk ke scope. Saran kompilator persis menunjuk perbaikannya — tambahkan impl<T>.

Method yang Menukar Posisi Parameter #

Method pada blok impl<T> tidak hanya memakai parameter struct — method boleh menyusun tipe baru darinya, termasuk menukar posisi. Struct Pair<A, B> berikut mendapat method swap yang membalik urutan kedua field:

rust
struct Pair<A, B> {
    first: A,
    second: B,
}

impl<A, B> Pair<A, B> {
    fn swap(self) -> Pair<B, A> {
        Pair { first: self.second, second: self.first }
    }
}

fn main() {
    let player = Pair { first: String::from("Budi"), second: 7 };
    let flipped = player.swap();

    println!("Nomor {} dipakai {}", flipped.first, flipped.second);
}

Output yang Dihasilkan:

text
Nomor 7 dipakai Budi

Mari kita bedah kode di atas baris per baris:

  1. Baris 7 (fn swap(self) -> Pair<B, A> {): tipe kembali menukar posisi parameter — Pair<B, A>. Penerima self tanpa & berarti method ini mengonsumsi instance, mengikuti tiga bentuk self dari Chapter Metode dengan impl; setelah swap dipanggil, player tidak bisa dipakai lagi.
  2. Baris 8 (Pair { first: self.second, second: self.first }): field second (bertipe B) pindah ke posisi first, dan sebaliknya. Kedua nilai berpindah kepemilikan dari instance lama ke instance baru tanpa penyalinan.
  3. Baris 14 (let flipped = player.swap();): instansiasi masuk Pair<String, i32>, instansiasi keluar Pair<i32, String> — transformasi murni di level tipe, dan flipped.first kini bertipe i32 sedangkan flipped.second bertipe String.

Method juga boleh mendeklarasikan type parameter tambahan miliknya sendiri — misalnya fn merge<C>(self, other: Pair<A, B>) -> Pair<A, C> — saat method menerima nilai bertipe yang tidak tercatat pada struct. Pola ini umum pada method koleksi di standard library; kita cukup ingat aturan urutannya: parameter struct dideklarasikan pada impl<A, B>, parameter milik method dideklarasikan pada fn nama<C>(...).

impl untuk Satu Instansiasi Saja #

Kadang sebuah perilaku hanya masuk akal untuk satu instansiasi tertentu. to_uppercase() adalah operasi teks — memberikan method ini pada Locker<i32> jelas tidak bermakna. Rust mengizinkan blok impl dengan tipe konkret:

rust
struct Locker<T> {
    content: T,
}

impl<T> Locker<T> {
    fn reveal(&self) -> &T {
        &self.content
    }
}

impl Locker<String> {
    fn shout(&self) -> String {
        self.content.to_uppercase()
    }
}

fn main() {
    let note = Locker { content: String::from("rapat pukul 9") };

    println!("Bentuk asli: {}", note.reveal());
    println!("Bentuk teriak: {}", note.shout());
}

Output yang Dihasilkan:

text
Bentuk asli: rapat pukul 9
Bentuk teriak: RAPAT PUKUL 9

Mari kita bedah kode di atas baris per baris:

  1. Baris 11 (impl Locker<String> {): blok kedua tidak lagi generik — T digantikan String secara langsung, tanpa deklarasi parameter. Seluruh method di dalamnya hanya dimiliki oleh instansiasi Locker<String>.
  2. Baris 12-14 (fn shout dan self.content.to_uppercase()): pemanggilan to_uppercase() hanya mungkin karena di blok ini self.content pasti bertipe String. Blok generik impl<T> tidak bisa memanggilnya karena T polos tidak menjanjikan method apa pun — pagar yang sama dengan Chapter Fungsi Generik.
  3. Baris 19-22 (pemakaian): note bertipe Locker<String> sehingga dua method tersedia sekaligus — reveal dari blok generik, shout dari blok khusus.

Pemanggilan method khusus pada instansiasi lain gagal dengan pesan yang sangat jujur:

rust
struct Locker<T> {
    content: T,
}

impl<T> Locker<T> {
    fn reveal(&self) -> &T {
        &self.content
    }
}

impl Locker<String> {
    fn shout(&self) -> String {
        self.content.to_uppercase()
    }
}

fn main() {
    let note = Locker { content: String::from("rapat pukul 9") };
    println!("{}", note.shout());

    let code = Locker { content: 4582 };
    println!("{}", code.shout());
}

Pesan error kompilator:

text
error[E0599]: no method named `shout` found for struct `Locker<{integer}>` in the current scope
  --> main.rs:22:25
   |
 1 | struct Locker<T> {
   | ---------------- method `shout` not found for this struct
...
22 |     println!("{}", code.shout());
   |                         ^^^^^ method not found in `Locker<{integer}>`
   |
   = note: the method was found for `Locker<String>`

error: aborting due to 1 previous error

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

Pesan E0599 memisahkan dua hal dengan rapi: method shout memang ada di struct Locker, tetapi hanya untuk Locker<String> — baris catatannya menyebut hal itu hampir harfiah. Loker bernomor (Locker<i32>) hanya memiliki method dari blok impl<T>.

Ringkasan #

Blok impl<T> Locker<T> melayani semua instansiasi sekaligus dan wajib mendeklarasikan ulang parameternya — kelalaian ditolak sebagai E0425 lengkap dengan saran perbaikan. Method di dalamnya bebas memakai T pada parameter dan nilai kembali, bahkan menyusun tipe baru seperti swap(self) -> Pair<B, A>, serta boleh membawa type parameter tambahan milik method sendiri. Blok impl Locker<String> menutup spektrum di ujung lain: perilaku eksklusif satu instansiasi, dengan E0599 sebagai penjaga batas. Satu tema masih tersisa dan akan mengikat seluruh section: T polos tidak bisa apa-apa — Chapter Trait Bound memberi kunci pertamanya.