Tipe Generik, Traits, dan Lifetimes
Setiap bahasa pemrograman punya tools buat menangani duplikasi konsep secara efektif. Di Rust, salah satu tool itu adalah generics (generik): pengganti abstrak buat tipe konkret atau properti lainnya. Kita bisa mengekspresikan perilaku dari generik atau gimana mereka berhubungan dengan generik lainnya tanpa perlu tau apa yang bakal menempati posisi mereka saat kode di-compile dan dijalankan.
Fungsi bisa menerima parameter dari suatu tipe generik, bukannya tipe konkret
seperti i32 atau String, dengan cara yang sama seperti mereka menerima
parameter dengan nilai yang belum diketahui untuk menjalankan kode yang sama di
beberapa nilai konkret. Sebenarnya, kita sudah memakai generik di Bab 6 dengan
Option<T>, di Bab 8 dengan Vec<T> dan HashMap<K, V>, serta di Bab 9
dengan Result<T, E>. Di bab ini, kita bakal eksplor gimana cara
mendefinisikan tipe, fungsi, dan method kita sendiri pakai generik!
Pertama-tama kita bakal mengulang gimana cara mengekstrak sebuah fungsi buat mengurangi duplikasi kode. Kemudian kita bakal memakai teknik yang sama buat bikin fungsi generik dari dua fungsi yang hanya berbeda di tipe parameternya. Kita juga bakal menjelaskan gimana cara memakai tipe generik di dalam definisi struct dan enum.
Setelah itu, kita bakal belajar gimana cara memakai traits untuk mendefinisikan perilaku dengan cara yang generik. Kita bisa menggabungkan traits dengan tipe generik untuk membatasi tipe generik agar cuma menerima tipe yang punya perilaku tertentu, dan tidak asal menerima tipe apa saja.
Terakhir, kita bakal membahas lifetimes: berbagai jenis generik yang memberi compiler informasi soal gimana referensi saling berhubungan. Lifetimes memungkinkan kita ngasih informasi yang cukup ke compiler soal borrowed values (nilai yang dipinjam) agar compiler bisa memastikan referensinya bakal valid di lebih banyak situasi yang tidak akan dia sanggup lakukan tanpa bantuan kita.
Menghapus Duplikasi dengan Mengekstrak Fungsi
Generik memungkinkan kita mengganti tipe spesifik pakai placeholder (tempat pengganti) yang mewakili banyak tipe buat menghilangkan duplikasi kode. Sebelum masuk ke sintaks generik, mari kita lihat dulu gimana cara menghilangkan duplikasi pakai cara yang tidak melibatkan tipe generik, yaitu dengan mengekstrak sebuah fungsi yang menggantikan nilai spesifik pakai placeholder yang mewakili banyak nilai. Habis itu, kita bakal menerapkan teknik yang sama buat mengekstrak fungsi generik! Dengan melihat gimana cara mengenali kode duplikat yang bisa diekstrak jadi sebuah fungsi, kita bakal mulai terbiasa mengenali kode duplikat yang bisa memakai generik.
Kita bakal mulai dari program pendek di Listing 10-1 yang mencari angka paling besar di dalam sebuah daftar (list).
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
assert_eq!(*largest, 100);
}
Kita menyimpan daftar integer di variabel number_list dan menaruh referensi
ke angka pertama di daftar itu di variabel bernama largest. Kemudian kita
iterasi melewati semua angka di daftar itu, dan kalau angka saat ini lebih
besar dari angka yang disimpan di largest, kita mengganti referensi di
variabel itu. Tapi, kalau angka saat ini lebih kecil atau sama dengan angka
paling besar yang dilihat sejauh ini, variabelnya tidak berubah, dan kodenya
lanjut ke angka berikutnya di daftar itu. Setelah memeriksa semua angka di
daftar, largest seharusnya merujuk ke angka paling besar, yang di kasus ini
adalah 100.
Sekarang kita dapat tugas buat mencari angka paling besar di dua daftar angka yang berbeda. Untuk melakukan itu, kita bisa memilih buat menduplikasi kode di Listing 10-1 lalu memakai logika yang sama di dua tempat yang berbeda di program, seperti yang ditunjukkan di Listing 10-2.
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
}
Walaupun kode ini jalan, menduplikasi kode itu merepotkan dan rawan error. Kita juga harus ingat buat meng-update kodenya di banyak tempat kalau kita mau mengubahnya.
Buat menghilangkan duplikasi ini, kita bakal bikin abstraksi dengan mendefinisikan sebuah fungsi yang beroperasi pada daftar integer apa pun yang dimasukkan sebagai parameternya. Solusi ini bikin kode kita lebih jelas dan memungkinkan kita mengekspresikan konsep pencarian angka paling besar di sebuah daftar secara abstrak.
Di Listing 10-3, kita mengekstrak kode yang mencari angka paling besar ke dalam
fungsi bernama largest. Terus kita panggil fungsi itu buat mencari angka
paling besar di dua daftar dari Listing 10-2. Kita juga bisa memakai fungsi itu
di daftar nilai i32 lain yang mungkin kita punya di masa depan.
fn largest(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {result}");
assert_eq!(*result, 100);
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let result = largest(&number_list);
println!("The largest number is {result}");
assert_eq!(*result, 6000);
}
Fungsi largest punya parameter bernama list, yang mewakili slice konkret
apa pun dari nilai i32 yang mungkin kita masukkan ke fungsi tersebut. Hasilnya,
pas kita memanggil fungsinya, kodenya jalan di nilai-nilai spesifik yang kita
masukkan.
Sebagai ringkasan, ini langkah-langkah yang kita ambil buat ngubah kode dari Listing 10-2 jadi Listing 10-3:
- Kenali kode yang terduplikasi.
- Ekstrak kode yang terduplikasi ke dalam body fungsi, lalu tentukan input dan nilai kembalian dari kode itu di signature fungsinya.
- Update dua instance kode yang terduplikasi buat memanggil fungsinya sebagai gantinya.
Berikutnya, kita bakal pakai langkah-langkah yang sama ini dengan generik buat
mengurangi duplikasi kode. Sama seperti body fungsi yang bisa beroperasi di list
abstrak bukannya nilai spesifik, generik memungkinkan kode buat beroperasi di
tipe abstrak.
Misalnya, katakanlah kita punya dua fungsi: satu yang mencari item paling besar
di slice nilai i32 dan satu lagi yang mencari item paling besar di slice
nilai char. Gimana cara kita menghilangkan duplikasi itu? Mari kita cari tahu!