Generik di Rust
Sebenarnya kamu sudah memakai generics (generik) tanpa sadar: Vec<i32> dan Option<T> sama-sama memakainya. Di halaman ini kamu akan belajar menulis fungsi dan struct generik buatanmu sendiri.
Masalah yang diselesaikan generics
Section titled “Masalah yang diselesaikan generics”Misalnya kamu butuh fungsi yang mengembalikan angka terbesar dari sebuah daftar, untuk i32 dan juga f64. Tanpa generics, kamu harus menulisnya dua kali:
fn largest_i32(numbers: &[i32]) -> i32 { let mut largest = numbers[0]; for &n in numbers { if n > largest { largest = n; } } largest}
fn largest_f64(numbers: &[f64]) -> f64 { let mut largest = numbers[0]; for &n in numbers { if n > largest { largest = n; } } largest}Logikanya sama persis, hanya disalin-tempel untuk tiap tipe. Dengan generics, cukup ditulis sekali.
Fungsi generik
Section titled “Fungsi generik”fn largest<T: PartialOrd + Copy>(numbers: &[T]) -> T { let mut largest = numbers[0]; for &n in numbers { if n > largest { largest = n; } } largest}
fn main() { let integers = [10, 25, 3, 47, 8]; let floats = [1.5, 2.8, 0.3];
println!("{}", largest(&integers)); println!("{}", largest(&floats));}Output:
472.8<T: PartialOrd + Copy> bisa dibaca “untuk tipe T apa saja, asalkan bisa dibandingkan (PartialOrd) dan disalin (Copy).” T hanyalah penanda tempat: Rust membuat versi fungsi ini untuk tipe konkret apa pun yang benar-benar kamu pakai saat memanggilnya (i32, f64, dan seterusnya).
Struct generik
Section titled “Struct generik”Struct juga bisa generik:
struct Pair<T> { first: T, second: T,}
fn main() { let numbers = Pair { first: 5, second: 10 }; let words = Pair { first: "halo", second: "dunia" };
println!("{} {}", numbers.first, numbers.second); println!("{} {}", words.first, words.second);}Output:
5 10halo duniaSatu definisi Pair<T> bisa dipakai untuk angka, teks, atau tipe lain apa pun. numbers bertipe Pair<i32> dan words bertipe Pair<&str>, tapi kamu hanya menulis struct-nya sekali.
Apakah ini memperlambat program?
Section titled “Apakah ini memperlambat program?”Tidak. Rust membuat versi khusus dari fungsi atau struct untuk setiap tipe konkret yang benar-benar kamu pakai (proses ini disebut monomorphization). Saat program berjalan, sudah tidak ada lagi kode generik, yang tersisa hanya fungsi biasa dengan tipe yang jelas, sama cepatnya seperti kalau kamu menulis masing-masing secara manual.
Method pada struct generik
Section titled “Method pada struct generik”Kamu bisa menambahkan method ke struct generik dengan blok impl, sama seperti di halaman Struct. Bedanya cuma satu: impl-nya juga butuh <T> sendiri:
struct Pair<T> { first: T, second: T,}
impl<T> Pair<T> { fn new(first: T, second: T) -> Self { Pair { first, second } }
fn swap(self) -> Pair<T> { Pair { first: self.second, second: self.first } }}
impl Pair<f64> { fn average(&self) -> f64 { (self.first + self.second) / 2.0 }}
fn main() { let words = Pair::new("kiri", "kanan").swap(); println!("{} {}", words.first, words.second);
let scores = Pair::new(7.0, 9.0); println!("{}", scores.average());}Output:
kanan kiri8Ada dua jenis blok impl di sini:
impl<T> Pair<T>berarti “method-method ini ada untukPairdengan tipe apa saja”. Baca<T>yang pertama sebagai “untuk tipeTapa pun”. Tanpa itu, Rust akan mengiraTadalah nama tipe sungguhan dan protes karena tidak bisa menemukannya.impl Pair<f64>tidak punya<T>. Method di dalamnya hanya ada untukPair<f64>. Itulah sebabnyaaverageboleh memakai+dan/ 2.0. Coba panggilwords.average(), dan compiler akan memberi tahu bahwa method itu tidak ada untukPair<&str>.
Struct juga bisa punya lebih dari satu parameter tipe, misalnya struct Pair<T, U> { first: T, second: U }, kalau kedua field-nya boleh berbeda tipe. Dan kalau trait bound-nya mulai panjang, kamu bisa memindahkannya ke klausa where supaya signature fungsi tetap mudah dibaca. Itu dibahas di halaman Trait lanjutan.
Latihan
Section titled “Latihan”1. Versi generik untuk nilai terkecil
Section titled “1. Versi generik untuk nilai terkecil”Tulis fn smallest<T: PartialOrd + Copy>(items: &[T]) -> T yang mengembalikan item terkecil. Uji dengan [8, 3, 5] dan dengan karakter ['r', 'u', 's', 't']. Petunjuk: ini sama dengan fungsi largest di halaman ini, hanya satu perbandingannya dibalik.
Lihat solusi
fn smallest<T: PartialOrd + Copy>(items: &[T]) -> T { let mut smallest = items[0]; for &item in items { if item < smallest { smallest = item; } } smallest}
fn main() { println!("{}", smallest(&[8, 3, 5])); println!("{}", smallest(&['r', 'u', 's', 't']));}Output:
3r2. Bagian yang lebih besar dari sebuah Pair
Section titled “2. Bagian yang lebih besar dari sebuah Pair”Tambahkan method larger(&self) -> &T ke Pair<T> yang mengembalikan referensi ke field mana pun yang lebih besar. Uji dengan Pair berisi angka dan Pair berisi kata. Petunjuk: membandingkan butuh PartialOrd, jadi taruh bound-nya langsung di impl: impl<T: PartialOrd> Pair<T>.
Lihat solusi
struct Pair<T> { first: T, second: T,}
impl<T: PartialOrd> Pair<T> { fn larger(&self) -> &T { if self.first > self.second { &self.first } else { &self.second } }}
fn main() { let numbers = Pair { first: 3, second: 9 }; let words = Pair { first: "pir", second: "apel" };
println!("{}", numbers.larger()); println!("{}", words.larger());}Output:
9pirString dibandingkan menurut urutan abjad, jadi "pir" dianggap lebih besar daripada "apel".
Ringkasan
Section titled “Ringkasan”- Generics memungkinkan kamu menulis satu fungsi atau struct yang bekerja untuk banyak tipe, tanpa menduplikasi kode.
<T>adalah tipe penanda tempat; trait bound (T: PartialOrd) membatasi tipe apa saja yang boleh dipakai sebagaiT.impl<T> Pair<T>menambahkan method untuk semuaT;impl Pair<f64>menambahkan method hanya untuk satu tipe konkret.- Kode generik dikompilasi menjadi versi khusus untuk tiap tipe, jadi tidak ada biaya performa saat program berjalan.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Masalah apa yang diselesaikan generics?
2. Pada fn largest<T: PartialOrd + Copy>(...), apa fungsi PartialOrd + Copy?
3. Apakah memakai generics menambah biaya saat runtime dibanding kode yang ditulis manual?
4. Apa yang bisa kamu lakukan dengan satu definisi struct Pair<T>?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- Generic Data Types — The Rust Book (bahasa Inggris): generics pada fungsi, struct, enum, dan method, termasuk contoh
largest. - Generics — Rust by Example (bahasa Inggris): contoh singkat yang bisa dijalankan tentang fungsi generik, struct generik, dan sintaksnya.
- Implementation — Rust by Example (bahasa Inggris): lebih lanjut tentang blok
impl<T>dan cara mengimplementasikan method untuk satu tipe konkret. - Bounds — Rust by Example (bahasa Inggris): bagaimana trait bound membatasi tipe generik, dan kenapa kamu membutuhkannya.
- Generic parameters — The Rust Reference (bahasa Inggris): aturan lengkapnya, kalau kamu ingin tahu detailnya.