Iterators

Iterator adalah konstruksi yang dapat memberi Anda item yang berada dalam collection, satu demi satu. Sebenarnya, kita telah menggunakan iterator berkali-kali: for loop memberikan Anda iterator. Saat Anda ingin menggunakan iterator di lain waktu, Anda harus memilih apa jenis iteratornya:

  • .iter() untuk iterator yang didapatkan dari references
  • .iter_mut() untuk iterator yang didapatkan dari mutable references
  • .into_iter() untuk iterator yang langsung didapatkan dari valuenya (bukan references)

for loop sebenarnya hanyalah iterator yang akan memiliki (owns) valuenya. Itulah kenapa for loop bisa membuatnya mutable dan kemudian Anda bisa mengubah valuenya di saat Anda menggunakannya.

Anda bisa menggunakan iterator seperti ini:

fn main() {
    let vector1 = vec![1, 2, 3]; // kita akan menggunakan .iter() dan .into_iter() pada vector1
    let vector1_a = vector1.iter().map(|x| x + 1).collect::<Vec<i32>>();
    let vector1_b = vector1.into_iter().map(|x| x * 10).collect::<Vec<i32>>();

    let mut vector2 = vec![10, 20, 30]; // kita akan menggunakan .iter_mut() pada vector2
    vector2.iter_mut().for_each(|x| *x +=100);

    println!("{:?}", vector1_a);
    println!("{:?}", vector2);
    println!("{:?}", vector1_b);
}

Hasilnya adalah:

[2, 3, 4]
[110, 120, 130]
[10, 20, 30]

Pada dua contoh dari vector 1, kita menggunakan method bernama .map(). Method ini akan melakukan sesuatu ke setiap element, dan kemudian di pass ke method selanjutnya. Pada vector2 kita menggunakan .for_each(). Method ini juga akan melakukan sesuatu ke setiap element, namun dengan cara yang berbeda. .iter_mut() plus for_each(), sebenarnya sama dengan for loop. Di dalam setiap method kita bisa memberikan sebuah nama kepada setiap element (kita menyebutnya dengan x) dan menggunakannya untuk mengubah isinya. Hal ini dikenal dengan nama closure dan kita akan mempelajarinya pada bagian selanjutnya.

Mari kita bahas lagi tentang itereator, satu per satu.

Pertama-tama, kita menggunakan .iter() pada vector1 untuk mendapatkan reference. Kita menambahkan 1 pada setiap elementnya, dan membuatnya menjadi Vec yang baru. vector1 tetap hidup, karena kita hanya menggunakan referencenya: kita sama sekali tidak mengambil valuenya. Sekarang kita memiliki vector1, dan sebuah Vec baru bernama vector1_a. Karena .map() hanya sekedar melakukan pass, maka kita perlu untuk menggunakan .collect() untuk membuatnya menjadi Vec.

Kemudian kita menggunakan into_iter untuk mendapatkan iterator berdasarkan value dari vector1. Ini membuat vector1 hancur/hangus, karena memang begitulah cara into_iter() bekerja. Jadinya, setelah kita membuat vector1_b, kita tidak bisa lagi menggunakan vector1.

Akhirnya, kita menggunkan .iter_mut() pada vector2. Ia mutable, sehingga kita tidak perlu untuk menggunakan .collect() untuk membuat Vec baru. Alih-alih membuat Vec baru, kita bisa mengubah value di Vec yang sama dengan menggunakan mutable references. Jadi, vector2 tetap hidup. Karena kita tidak perlu Vec yang baru, kita cukup menggunakan for_each: ia mirip seperti for loop.

How an iterator works

Sebuah iterator bekerja dengan menggunakan method bernama .next(), yang mana ia akan mengembalikan Option. Di saat Anda menggunakan an iterator, Rust selalu menggunakan next() berulang-ulang. Jika ia mendapatkan Some, ia akan berlanjut. Jika ia mendapatkan None, ia akan berhenti.

Apa Anda masih mengingat tentang macro assert_eq!? Jika Anda sering membuka dokumentasi, Anda akan selalu melihatnya. Code dibawah ini menunjukkan bagaimana iterator bekerja.

fn main() {
    let my_vec = vec!['a', 'b', '거', '柳']; // Hanya Vec biasa

    let mut my_vec_iter = my_vec.iter(); // Sekarang typenya menjadi Iterator, tapi kita belum bisa melakukan call (memanggilnya) sekarang

    assert_eq!(my_vec_iter.next(), Some(&'a'));  // Panggil (call) element pertama menggunakan .next()
    assert_eq!(my_vec_iter.next(), Some(&'b'));  // panggil element selanjutnya
    assert_eq!(my_vec_iter.next(), Some(&'거')); // Lagi
    assert_eq!(my_vec_iter.next(), Some(&'柳')); // Lagi
    assert_eq!(my_vec_iter.next(), None);        // Tidak ada yang tersissa: kembaliannya adalah None
    assert_eq!(my_vec_iter.next(), None);        // Anda tetap bisa menggunakan .next(), namun hasilnya tetaplah None
}

Mengimplementasikan Iterator untuk struct atau enum yang kita buat tidaklah terlalu sulit. Pertama-tama, kita akan membuat tentang perpustakaan.

#[derive(Debug)] // kita ingin melakukan print dengan {:?}
struct Library {
    library_type: LibraryType, // ini adalah enum yang kita buat
    books: Vec<String>, // daftar buku
}

#[derive(Debug)]
enum LibraryType { // perpustakaan bisa jadi adalah perpustakaan kota atau perpustakaan negara
    City,
    Country,
}

impl Library {
    fn add_book(&mut self, book: &str) { // kita menggunakan add_book untuk menambahkan buku
        self.books.push(book.to_string()); // kita mengambil &str dan mengubahnya menjadi String, dan menambahkannya ke dalam Vec
    }

    fn new() -> Self { // membuat Library yang baru
        Self {
            library_type: LibraryType::City, // pada umumnya, perpustakaan yang baru adalah selalu perpustakaan kota, sehingga kita pilih City
            books: Vec::new(),
        }
    }
}

fn main() {
    let mut my_library = Library::new(); // buat perpustakaan baru
    my_library.add_book("The Doom of the Darksword"); // tambahkan beberapa buku
    my_library.add_book("Demian - die Geschichte einer Jugend");
    my_library.add_book("구운몽");
    my_library.add_book("吾輩は猫である");

    println!("{:?}", my_library.books); // kita bisa print daftar dari buku-buku yang ada di perpustakaan tersebut
}

Code di atas berjalan dengan baik. Sekarang kita ingin mengimplementasikan Iterator pada perpustakaan tersebut, jadinya kita akan menggunakan for loop. Sekarang, jika kita menggunakan for loop, ia tidak berjalan:


#![allow(unused)]
fn main() {
for item in my_library {
    println!("{}", item); // ⚠️
}
}

Dan compiler akan mengatakan:

error[E0277]: `Library` is not an iterator
  --> src\main.rs:47:16
   |
47 |    for item in my_library {
   |                ^^^^^^^^^^ `Library` is not an iterator
   |
   = help: the trait `std::iter::Iterator` is not implemented for `Library`
   = note: required by `std::iter::IntoIterator::into_iter`

Tapi kita bisa membuat library tersebut menjadi sebuah iterator dengan impl Iterator for Library. Informasi tentang trait Iterator bisa dilihat pada dokumentasi standard library: https://doc.rust-lang.org/std/iter/trait.Iterator.html

Pada bagian kiri-atas di laman tersebut, ada tulisan: Associated Types: Item dan Required Methods: next. "Associated type" berarti "type yang berjalan bersama". Associated type (type yang akan berjalan bersama iterator kita) adalah String, karena kita ingin iterator memberikan kita String sebagai kembaliannya.

Pada laman tersebut ada contoh code yang terlihat seperti berikut:

// iterator yang hasilnya berada di antara Some dan None
struct Alternate {
    state: i32,
}

impl Iterator for Alternate {
    type Item = i32;

    fn next(&mut self) -> Option<i32> {
        let val = self.state;
        self.state = self.state + 1;

        // jika ia genap, kembalikan Some(i32). else None
        if val % 2 == 0 {
            Some(val)
        } else {
            None
        }
    }
}

fn main() {}

Anda bisa melihat bahwa di bawah impl Iterator for Alternate ada type Item = i32. Itulah associated type. Iterator kita adalah daftar buku, yang mana bertype Vec<String>. Di saat kita memanggil next, ia akan memberikan kita String. Sehingga kita akan menuliskan type Item = String;. Itulah associated item.

Untuk mengimplementasikan Iterator, Anda perlu menuliskan function fn next(). Ini adalah dimana Anda menentukan apa yang harus dilakukan oleh iterator. Untuk Library yang kita buat, kita ingin ia menampilkan buku yang terakhir ditampilkan di awal. Jadinya, kita akan lakukan match dengan .pop() yang mana akan mengambil element terakhir jika ia adalah Some. Kita juga ingin mencetak " is found!" untuk setiap item/element. Sekarang codenya terlihat seperti berikut:

#[derive(Debug, Clone)]
struct Library {
    library_type: LibraryType,
    books: Vec<String>,
}

#[derive(Debug, Clone)]
enum LibraryType {
    City,
    Country,
}

impl Library {
    fn add_book(&mut self, book: &str) {
        self.books.push(book.to_string());
    }

    fn new() -> Self {
        Self {
            library_type: LibraryType::City,
            books: Vec::new(),
        }
    }
}

impl Iterator for Library {
    type Item = String;

    fn next(&mut self) -> Option<String> {
        match self.books.pop() {
            Some(book) => Some(book + " is found!"), // Rust memperbolehkan String + &str
            None => None,
        }
    }
}

fn main() {
    let mut my_library = Library::new();
    my_library.add_book("The Doom of the Darksword");
    my_library.add_book("Demian - die Geschichte einer Jugend");
    my_library.add_book("구운몽");
    my_library.add_book("吾輩は猫である");

    for item in my_library.clone() { // sekarang kita bisa menggunakan for loop. Kita gunakan clone agar Library tidak hangus/hancur
        println!("{}", item);
    }
}

Hasilnya adalah:

吾輩は猫である is found!
구운몽 is found!
Demian - die Geschichte einer Jugend is found!
The Doom of the Darksword is found!