Portal/Notes πŸ“
Rust

Generics & Traits

Versi Bahasa Indonesia. Baca dalam Bahasa Inggris

Dengan menggunakan Generics dan Traits, kita dapat memanfaatkan kekuatan gabungan mereka untuk menulis kode yang efisien, dapat digunakan kembali, dan type-safe.

Generics

  • Generics digunakan untuk membuat definisi untuk item seperti function signature atau struct, yang kemudian dapat kita gunakan dengan banyak tipe data konkret yang berbeda.
  • Jadi, alih-alih menulis implementasi terpisah untuk setiap tipe, Anda mendefinisikan tipe generik yang dapat diganti dengan tipe konkret saat kode digunakan.
  • Direpresentasikan dengan angle brackets (<>) dan sering dilambangkan dengan T, U, atau nama deskriptif lainnya.
  • Generics di Rust diselesaikan saat compile time, memastikan bahwa tipe yang tidak valid tidak dapat digunakan.
  • Rust menghasilkan kode yang dioptimalkan untuk setiap tipe konkret yang digunakan dengan generic pada saat compile, menghindari runtime overhead.

Generic Functions

  • Fungsi dapat menggunakan generics untuk beroperasi pada banyak tipe.

    fn hello<T: std::fmt::Display>(name: T) {
        println!("hello {}!", name);
    }
    
    fn main() {
        hello(777);
        hello("World");
    }

Generic Structs

  • Struct dapat menyimpan nilai dari tipe apa pun menggunakan generics.

    struct Point<T> {
        x: T,
        y: T,
    }
    
    let int_point = Point { x: 1, y: 2 };
    let float_point = Point { x: 1.7, y: 2.4 };

Generic Enums

  • Enum juga dapat menggunakan generics, seperti yang terlihat pada Option atau Result.

    enum Option<T> {
        Some(T),
        None,
    }
    
    let int_option: Option<i32> = Option::Some(10);
    let float_option: Option<f64> = Option::Some(5.7);

Traits

  • Trait mirip dengan interface di bahasa pemrograman lain.
  • Ia mendefinisikan sekumpulan metode yang harus diimplementasikan oleh suatu tipe, memungkinkan Anda menentukan fungsionalitas apa yang disediakan suatu tipe tanpa mendikte bagaimana ia menyediakannya.
  • Mengimplementasikan trait pada suatu tipe mirip dengan mengimplementasikan metode reguler.
  • Perbedaannya adalah setelah impl, kita letakkan nama trait yang ingin kita implementasikan, lalu gunakan kata kunci for, dan kemudian tentukan nama tipe yang ingin kita implementasikan trait tersebut.
  • Traits dapat menyediakan implementasi metode default yang bisa di-override oleh tipe.
  • Sintaks impl Trait berfungsi untuk kasus yang sederhana tetapi sebenarnya adalah sintaks gula untuk bentuk yang lebih panjang yang dikenal sebagai trait bound.
  • Kita juga dapat menentukan lebih dari satu trait bound menggunakan sintaks +.

Mendefinisikan Trait

  • Sebuah trait mendefinisikan metode yang dapat diimplementasikan oleh tipe lain.

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

Mengimplementasikan Trait

  • Mengimplementasikan trait mirip dengan mengimplementasikan metode dengan meletakkan nama trait setelah impl dan kemudian menentukan tipe setelah for.

    struct NewsArticle {
        headline: String,
        location: String,
        author: String,
        content: String,
    }
    
    impl Summary for NewsArticle {
        fn summarize(&self) -> String {
            format!("{}, by {} ({})", self.headline, self.author, self.location)
        }
    }
    
    struct Tweet {
        username: String,
        content: String,
        reply: bool,
        retweet: bool,
    }
    
    impl Summary for Tweet {
        fn summarize(&self) -> String {
            format!("{}: {}", self.username, self.content)
        }
    }

Default Implementation

  • Traits dapat menyediakan implementasi metode default yang bisa di-override oleh tipe.

    trait Summary {
        fn summarize(&self) -> String {
            "Default Summary".to_string()
        }
    }

Traits sebagai Parameter

  • Traits dapat digunakan sebagai parameter menggunakan sintaks impl Trait.

    fn notify(item: &impl Summary) {
        println!("Breaking news! {}", item.summarize());
    }
  • Sintaks impl Trait berfungsi untuk kasus yang sederhana tetapi sebenarnya adalah sintaks gula untuk bentuk yang lebih panjang yang dikenal sebagai trait bound; bentuknya seperti ini:

    pub fn notify<T: Summary>(item: &T) {
        println!("Breaking news! {}", item.summarize());
    }
  • Kita juga bisa memiliki banyak parameter yang mengimplementasikan sebuah trait.

  • Misalnya, fungsi ini memiliki dua parameter yang mengimplementasikan Summary:

    pub fn notify(item1: &impl Summary, item2: &impl Summary) {
  • Kita juga bisa melakukannya menggunakan generics.

  • Tetapi dalam contoh di bawah, tipe generik T yang ditentukan sebagai tipe parameter item1 dan item2 membatasi fungsi tersebut sehingga tipe konkret dari nilai yang diberikan sebagai argumen untuk item1 dan item2 harus sama.

    pub fn notify<T: Summary>(item1: &T, item2: &T) {
  • Kita juga dapat menggunakan sintaks impl Trait di posisi return untuk mengembalikan nilai dari suatu tipe yang mengimplementasikan sebuah trait.

    fn returns_summarizable() -> impl Summary {
        NewsArticle {
            headline: "Good News",
            location: "Indonesia",
            author: "Someone",
            content: "Something",
        }
    }

Menentukan Multiple Trait Bounds

  • Kita juga dapat menentukan lebih dari satu trait bound menggunakan sintaks +.

    pub fn notify(item: &(impl Summary + Display)) {
  • Sintaks + juga valid dengan trait bounds pada tipe generik.

    pub fn notify<T: Summary + Display>(item: &T) {

Generic Traits

  • Traits juga bisa bersifat generik, memungkinkan mereka beroperasi pada berbagai tipe.

    trait Magic<T> {
        fn add(&self, other: T) -> T;
    }
    
    impl Magic<String> for i32 {
        fn add(&self, other: String) -> String {
            format!("{}:{}", self, other)
        }
    }
    
    fn main() {
        println!("{}", 71.add("Magic".to_string()));
    }

Memperluas Trait yang Ada

  • Kita dapat memperluas trait yang ada dengan menggabungkannya atau menambahkan perilaku tambahan.

    trait Printable: Display {
        fn print(&self) {
            println!("{}", self);
        }
    }
    
    impl Printable for i32 {}
    impl Printable for String {}
    
    let number: i32 = 71;
    number.print(); // Output: 71
    
    let text = String::from("FooBar");
    text.print(); // Output: FooBar

Referensi


Edit on GitHub

Last updated on

Generics & Traits | Faisal Affan