Bentuk penuh trait bound dari chapter sebelumnya memberi kendali penuh, tetapi harganya tampak pada setiap signature: hanya untuk menerima “sesuatu yang bisa diringkas”, kita harus menyiapkan nama tipe T, daftar generik <T: Summary>, lalu memakai &T di parameter. Untuk fungsi sederhana yang tidak butuh menyebut T lebih dari satu kali, seluruh upacara itu terasa berlebihan. Rust menyediakan bentuk ringkas yang menempatkan trait langsung pada tipe parameter: item: &impl Summary, dibaca “pinjaman dari tipe apa pun yang mengimplementasikan Summary”. Kompilator memperlakukan bentuk ini persis seperti versi panjangnya — sama-sama generik, sama-sama diperiksa, sama-sama dimonomorfisasi. Perbedaan besar justru tersembunyi di balik kesederhanaannya: setiap parameter &impl Summary adalah tipe anonim yang berdiri sendiri, dan makna itu berubah drastis ketika parameter muncul lebih dari satu kali.

Bentuk Singkat impl Trait pada Argumen #

Mari kita tulis ulang fungsi notify dari chapter sebelumnya dalam bentuk ringkasnya:

rust
trait Summary {
    fn summarize(&self) -> String;
}

struct NewsArticle {
    author: String,
    headline: String,
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{} menulis \"{}\"", self.author, self.headline)
    }
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn notify(item: &impl Summary) {
    println!("Kabar terbaru! {}", item.summarize());
}

fn main() {
    let article = NewsArticle {
        author: String::from("Rina Wijaya"),
        headline: String::from("Pasar Saham Jakarta Ditutup Menguat"),
    };

    let tweet = Tweet {
        username: String::from("budi_perjalanan"),
        content: String::from("Sampai di puncak Gunung Bromo!"),
    };

    notify(&article);
    notify(&tweet);
}

Output yang Dihasilkan:

text
Kabar terbaru! Rina Wijaya menulis "Pasar Saham Jakarta Ditutup Menguat"
Kabar terbaru! budi_perjalanan: Sampai di puncak Gunung Bromo!

Tiga bagian layak dibedah:

  1. Baris 27 (fn notify(item: &impl Summary)): tipe parameter kini ditulis &impl Summary. Kata kunci impl di posisi tipe berarti “beberapa tipe konkret yang mengimplementasikan Summary” — tipe mana pun boleh masuk, dan setiap pemanggilan tetap diperiksa terhadap kontrak.
  2. Baris 28 (println!("Kabar terbaru! {}", item.summarize());): di dalam body, item.summarize() sah persis seperti pada versi <T: Summary>. Bound yang melekat pada parameter cukup menjadi izin pemanggilan.
  3. Baris 42-43 (notify(&article); dan notify(&tweet);): dua pemanggilan dengan dua tipe berbeda melalui satu definisi. Di balik layar, kompilator menjalankan monomorphisasi yang sama dengan chapter sebelumnya — dua salinan fungsi dibuat, masing-masing dengan tipe konkret berbeda — sehingga performa dan pemeriksaan statis tidak berubah sedikit pun.

Bentuk fn notify(item: &impl Summary) dan fn notify<T: Summary>(item: &T) benar-benar setara untuk kasus satu parameter: kompilator menurunkan keduanya ke struktur internal yang sama. Pilihan di antara keduanya murni soal keterbacaan — bentuk ringkas menyembunyikan seluruh mekanisme generik di belakang tipe parameter.

Cara membaca signature keduanya pun layak dibiasakan. Tanda kurung sudut <T: Summary> dibaca dari kiri: “tetapkan nama T untuk tipe apa pun asalkan T mengimplementasikan Summary” — penamaan dulu, syarat kemudian. Bentuk &impl Summary memotong urutan itu: “pinjaman dari tipe yang mengimplementasikan Summary” — syarat langsung menjadi tipe. Nama T hilang dari pandangan, dan hilangnya nama itulah yang akan menentukan batas kegunaannya pada bagian berikutnya.

Dua Parameter, Dua Tipe Berbeda #

Perbedaan semantik yang sesungguhnya muncul saat parameter bertambah. Pada bentuk ringkas, setiap parameter &impl Summary membawa tipe anonimnya sendiri — keduanya tidak saling terkait. Program lengkap berikut memakai notify_pair dengan dua parameter tersebut dan mengirimkan dua tipe yang berbeda:

rust
trait Summary {
    fn summarize(&self) -> String;
}

struct NewsArticle {
    author: String,
    headline: String,
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{} menulis \"{}\"", self.author, self.headline)
    }
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn notify_pair(item1: &impl Summary, item2: &impl Summary) {
    println!("Item pertama: {}", item1.summarize());
    println!("Item kedua: {}", item2.summarize());
}

fn main() {
    let article = NewsArticle {
        author: String::from("Rina Wijaya"),
        headline: String::from("Pasar Saham Jakarta Ditutup Menguat"),
    };

    let tweet = Tweet {
        username: String::from("budi_perjalanan"),
        content: String::from("Sampai di puncak Gunung Bromo!"),
    };

    notify_pair(&article, &tweet);
}

Output yang Dihasilkan:

text
Item pertama: Rina Wijaya menulis "Pasar Saham Jakarta Ditutup Menguat"
Item kedua: budi_perjalanan: Sampai di puncak Gunung Bromo!

Baris 27 (fn notify_pair(item1: &impl Summary, item2: &impl Summary)) memuat dua tipe anonim yang berdiri sendiri — seolah ditulis <A: Summary, B: Summary>(item1: &A, item2: &B). Karena itu Baris 43 (notify_pair(&article, &tweet);) sah: item1 diikat ke NewsArticle dan item2 diikat ke Tweet tanpa konflik, dan keduanya tetap menjalankan implementasi summarize miliknya masing-masing.

Bandingkan dengan padanan bound eksplisit yang memakai satu T untuk kedua parameter — fn notify_pair<T: Summary>(item1: &T, item2: &T). Satu nama T berarti satu tipe untuk kedua parameter, sehingga pemanggilan yang sama di atas tidak lolos:

rust
trait Summary {
    fn summarize(&self) -> String;
}

struct NewsArticle {
    author: String,
    headline: String,
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{} menulis \"{}\"", self.author, self.headline)
    }
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn notify_pair<T: Summary>(item1: &T, item2: &T) {
    println!("Item pertama: {}", item1.summarize());
    println!("Item kedua: {}", item2.summarize());
}

fn main() {
    let article = NewsArticle {
        author: String::from("Rina Wijaya"),
        headline: String::from("Pasar Saham Jakarta Ditutup Menguat"),
    };
    let tweet = Tweet {
        username: String::from("budi_perjalanan"),
        content: String::from("Jalan alternatif dipenuhi mobil."),
    };

    notify_pair(&article, &tweet);
}

Pesan error kompilator:

text
error[E0308]: mismatched types
  --> main.rs:42:27
   |
42 |     notify_pair(&article, &tweet);
   |     -----------           ^^^^^^ expected `&NewsArticle`, found `&Tweet`
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected reference `&NewsArticle`
              found reference `&Tweet`
note: function defined here
  --> main.rs:27:4
   |
27 | fn notify_pair<T: Summary>(item1: &T, item2: &T) {
   |    ^^^^^^^^^^^                        ---------

error: aborting due to 1 previous error

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

Pesan E0308 menunjukkan proses penyimpulan yang terjadi: kompilator mengikat T dari argumen pertama (&NewsArticle), lalu memeriksa argumen kedua terhadap keputusan itu — dan menolak &Tweet. Satu nama T adalah satu janji kesamaan tipe; kalau maksud kita memang “dua item bertipe sama”, penolakan ini justru melindungi kontrak fungsi. Kalau maksud kita “dua item apa saja yang bisa diringkas”, dua pilihan tersedia: bentuk ringkas &impl Summary pada kedua parameter, atau padanan eksplisit dengan dua nama tipe <A: Summary, B: Summary>.

Membaca kembali signature fn notify_pair(item1: &impl Summary, item2: &impl Summary) kini terasa berbeda dari sekadar “versi pendek dari bound”: tanda impl Summary yang berdiri sendiri di tiap parameter adalah dua slot kosong yang saling bebas. Perbandingan kedua bentuk ini penting dihafal karena keduanya sering dipertukarkan secara tidak sengaja — terutama saat refactoring signature panjang menjadi pendek, di mana makna berubah tanpa satu pun baris body yang berubah.

Kapan Memilih Bentuk Singkat #

Pedoman pemilihannya mengikuti kebutuhan menyebut tipe parameter lagi di tempat lain:

  • Parameter tunggal yang tidak perlu disebut lagi — pakai &impl Summary. Signature pendek, niat jelas, pemeriksaan sama.
  • Butuh mengikat kesamaan tipe antar parameter atau dengan tipe kembalian — pakai bentuk eksplisit <T: Summary>. Hanya nama T yang bisa menyatakan “parameter kedua bertipe sama dengan yang pertama” atau “nilai kembalian bertipe sama dengan parameter”.
  • Kombinasi keduanya sah. Fungsi dengan tiga parameter boleh memakai T untuk dua parameter yang harus sama, sementara parameter ketiga memakai &impl Summary karena bebas.

Prinsip praktisnya: &impl Trait menjawab pertanyaan “tipe ini bisa apa?”, sedangkan T menjawab pertanyaan tambahan “tipe ini sama dengan tipe mana lagi?”. Kedua pertanyaan itu berbeda tingkat kegunaan — pertama tentang kemampuan, kedua tentang identitas — dan signature yang baik selalu tahu pertanyaan mana yang sedang dia tanyakan.

Ringkasan #

Sintaks &impl Trait adalah bentuk ringkas trait bound pada argumen: satu parameter cukup ditulis dengan trait-nya langsung, dan kompilator menurunkannya ke mekanisme generik-monomorphisasi yang sama persis dengan <T: Summary>. Ketika parameter bertambah, maknanya berbeda: setiap &impl Summary adalah tipe anonim mandiri sehingga dua parameter boleh berbeda tipe, sementara satu nama T yang dipakai dua kali menuntut kesamaan tipe dan menolak campuran dengan E0308. Pemilihan bentuk kini tinggal menimbang apakah tipe parameter perlu disebut lagi di signature. Babak impl Trait sesungguhnya belum selesai — kata kunci yang sama juga bisa duduk di posisi tipe kembalian, dan itulah pembahasan chapter berikutnya.