Halo teman-teman semuanya, pada artikel sebelumnya kita sudah mempelajari tentang Option dan Result di Rust. Kita melihat bagaimana Rust menangani nilai yang mungkin ada atau tidak ada menggunakan Option
, serta bagaimana Rust menangani operasi yang bisa sukses atau gagal menggunakan Result
. Keduanya adalah contoh nyata penggunaan generics di Rust.
Generics adalah fitur yang memungkinkan kita menulis kode yang fleksibel, dapat digunakan untuk berbagai tipe data, tetapi tetap aman. Dengan generics, kita bisa menghindari duplikasi kode dan membuat fungsi, struct, maupun enum yang lebih abstrak.
Generics bukan hanya sekadar sintaks untuk "tipe data bebas", tetapi juga bekerja erat dengan sistem tipe Rust untuk memastikan keamanan pada saat compile. Mari kita bahas satu per satu.
Fungsi dengan Generics
Tanpa generics, kita mungkin menulis dua fungsi berbeda untuk tipe data yang berbeda.
fn cetak_i32(x: i32) {
println!("i32: {}", x);
}
fn cetak_f64(x: f64) {
println!("f64: {}", x);
}
Dengan generics, kita bisa menulis satu fungsi yang berlaku untuk banyak tipe.
fn cetak<T>(x: T) {
println!("Value tersedia");
}
fn main() {
cetak(10); // i32
cetak(3.14); // f64
cetak("Rust"); // &str
}
Di sini kita menggunakan parameter generik T
. Rust akan menggantinya dengan tipe konkret saat fungsi dipanggil.
Struct dengan Generics
Struct juga bisa menggunakan generics agar field-nya bisa menampung berbagai tipe.
struct Point<T> {
x: T,
y: T,
}
fn main() {
let p1 = Point { x: 5, y: 10 }; // Point<i32>
let p2 = Point { x: 1.2, y: 3.4 }; // Point<f64>
println!("p1: ({}, {})", p1.x, p1.y);
println!("p2: ({}, {})", p2.x, p2.y);
}
Point<T>
bisa menampung i32
maupun f64
, tergantung tipe data yang digunakan.
Enum dengan Generics
Enum Option<T>
dan Result<T, E>
yang sudah kita pelajari sebelumnya adalah contoh penggunaan generics. Kita juga bisa membuat enum generik sendiri.
enum MyOption<T> {
Some(T),
None,
}
fn main() {
let angka = MyOption::Some(100);
let kosong: MyOption<i32> = MyOption::None;
match angka {
MyOption::Some(n) => println!("Angka: {}", n),
MyOption::None => println!("Tidak ada nilai"),
}
match kosong {
MyOption::Some(n) => println!("Angka: {}", n),
MyOption::None => println!("Kosong"),
}
}
Multiple Generics
Kita juga bisa menggunakan lebih dari satu parameter generik.
struct Pair<T, U> {
first: T,
second: U,
}
fn main() {
let data1 = Pair { first: 10, second: "Rust" };
let data2 = Pair { first: 3.14, second: true };
println!("Pair1: {}, {}", data1.first, data1.second);
println!("Pair2: {}, {}", data2.first, data2.second);
}
Dengan Pair<T, U>
, kita bisa menggabungkan dua nilai dengan tipe yang berbeda.
Batasan Generics (Trait Bound)
Kadang kita ingin membatasi generics agar hanya menerima tipe tertentu. Caranya adalah dengan trait bound.
fn cetak<T: std::fmt::Display>(x: T) {
println!("Isi: {}", x);
}
fn main() {
cetak(42);
cetak("Halo Rust");
// cetak(vec![1, 2, 3]); // error: Vec<i32> tidak implement Display
}
Dengan menambahkan T: Display
, hanya tipe yang mengimplementasikan trait Display
yang bisa digunakan.
Kesimpulan
Pada artikel ini kita sudah mempelajari tentang generics di Rust. Kita melihat bagaimana generics membuat fungsi, struct, dan enum menjadi lebih fleksibel, bagaimana menggunakan lebih dari satu parameter generik, serta bagaimana membatasi generics dengan trait bound.
Generics adalah salah satu fitur penting di Rust karena memungkinkan kita menulis kode yang reusable tanpa kehilangan keamanan tipe.
Pada artikel berikutnya, kita akan membahas tentang Trait di Rust, yang memungkinkan kita mendefinisikan perilaku yang bisa dibagi ke berbagai tipe.
Terima Kasih