Pada chapter sebelumnya kita melihat bahwa for name in menu.iter() berhasil mengiterasi koleksi tanpa mengganggunya, dan next yang dipanggil manual menghasilkan nilai bertipe reference. Sebenarnya Rust menyediakan tiga method untuk memulai iterasi — iter, iter_mut, dan into_iter — dan ketiganya memetakan persis ke tiga cara mengakses data yang sudah kita kuasai di section Ownership: membaca lewat pinjaman &T, mengubah lewat pinjaman &mut T, atau memiliki nilai itu sendiri T. Memilih bentuk yang tepat berarti memilih konsekuensi kepemilikan yang tepat pula, karena salah satu bentuk akan memindahkan koleksi kita secara permanen. Pada chapter ini kita bedah ketiganya satu per satu, lengkap dengan bentuk singkat for yang menjadi gula sintaksisnya.

Tiga Wajah Iterasi pada Vec #

Sebelum masuk ke kode, mari kita petakan pilihan yang tersedia untuk sebuah koleksi, misalnya Vec<String> bernama menu:

BentukMethodElemen yang dihasilkanKonsekuensi kepemilikan
Pinjam bacamenu.iter()&Stringmenu tetap milik kita dan tidak berubah
Pinjam ubahmenu.iter_mut()&mut Stringmenu tetap milik kita, isinya boleh diubah di tempat
Pindahkanmenu.into_iter()Stringkepemilikan elemen berpindah ke perulangan; koleksi kosong

Tabel di atas adalah cerminan langsung aturan borrowing: satu jalur baca bersama, satu jalur tulis eksklusif, atau penyerahan kepemilikan penuh. Tidak ada bentuk keempat, dan kompilator akan memastikan kita tidak mendapatkan lebih dari yang dibayarkan.

iter: Meminjam untuk Membaca #

Bentuk paling sering dipakai sehari-hari adalah iter(). Iterator hasilnya menyerahkan reference ke setiap elemen, sehingga koleksi aslinya sama sekali tidak tersentuh:

rust
fn main() {
    let menu = vec![String::from("Kopi Gayo"), String::from("Teh Melati")];

    for name in menu.iter() {
        println!("Menu: {}", name);
    }

    println!("Koleksi masih utuh: {} item", menu.len());
}

Output yang Dihasilkan:

text
Menu: Kopi Gayo
Menu: Teh Melati
Koleksi masih utuh: 2 item

Mari kita bedah baris demi baris:

  1. Baris 2 (let menu = vec![...]): menu menjadi pemilik Vec<String> berisi dua nama minuman.
  2. Baris 4 (for name in menu.iter()): menu.iter() meminjam koleksi secara immutable dan membuat iterator yang menyerahkan &String per putaran. Variabel name bertipe &String, bukan String — sesuai dengan yang kita lihat pada output Some(15000) bertipe &i32 di chapter sebelumnya.
  3. Baris 5 (println!("Menu: {}", name);): pemformatan membaca isi String melalui reference tanpa memindahkan atau mengklonnya.
  4. Baris 8 (menu.len()): setelah perulangan selesai, menu masih hidup dan dapat dipakai penuh — bukti bahwa iter() hanya meminjam. Pinjaman baca juga boleh dipegang bersamaan dengan pengaksesan lain, sehingga kita bisa membuat dua iterator iter() dari koleksi yang sama tanpa konflik.

into_iter: Memindahkan Kepemilikan #

Bentuk kedua, into_iter(), memulai iterasi dengan menyerahkan kepemilikan koleksi itu sendiri. Kata into pada namanya adalah petunjuk: data masuk ke dalam iterator dan tidak kembali. Setiap elemen kini menjadi milik badan perulangan:

rust
fn main() {
    let menu = vec![String::from("Kopi Gayo"), String::from("Teh Melati")];

    for name in menu.into_iter() {
        println!("Nama menu: {}", name);
    }
}

Output yang Dihasilkan:

text
Nama menu: Kopi Gayo
Nama menu: Teh Melati

Program ini berjalan mulus, tetapi ada harga yang harus dibayar. Coba kita tambahkan satu baris setelah perulangan yang mengakses menu seperti pada contoh iter() sebelumnya:

rust
fn main() {
    let menu = vec![String::from("Kopi Gayo"), String::from("Teh Melati")];

    for name in menu.into_iter() {
        println!("Menu: {}", name);
    }

    println!("Koleksi masih utuh: {} item", menu.len());
}

Kompilator menolak program tersebut dengan pesan galat asli berikut:

text
error[E0382]: borrow of moved value: `menu`
 --> main.rs:8:45
  |
2 |     let menu = vec![String::from("Kopi Gayo"), String::from("Teh Melati")];
  |         ---- move occurs because `menu` has type `Vec<String>`, which does not implement the `Copy` trait
3 |
4 |     for name in menu.into_iter() {
  |                      ----------- `menu` moved due to this method call
...
8 |     println!("Koleksi masih utuh: {} item", menu.len());
  |                                             ^^^^ value borrowed here after move
  |
note: `std::iter::IntoIterator::into_iter` takes ownership of the receiver `self`, which moves `menu`
 --> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/iter/traits/collect.rs:312:17
help: you can `clone` the value and consume it, but this might not be your desired behavior
  |
4 |     for name in menu.clone().into_iter() {
  |                     ++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0382`.

Pesan E0382 ini persis dengan pesan kepemilikan yang kita temui di section Ownership: menu berpindah karena pemanggilan into_iter() pada baris 4, lalu menu.len() pada baris 8 mencoba meminjam nilai yang sudah tidak dimiliki siapa pun di scope ini. Perhatikan pula catatan kompilator: std::iter::IntoIterator::into_iter secara eksplisit mengambil kepemilikan receiver self. Inilah bukti tertulis bahwa memilih into_iter() sama dengan menyerahkan koleksi secara penuh.

iter_mut: Meminjam untuk Mengubah #

Bentuk ketiga menyelesaikan kebutuhan yang tidak bisa dipenuhi dua bentuk lainnya: mengubah setiap elemen di tempat. Toko kopi kita sedang mengadakan promosi potongan Rp1.000 untuk seluruh menu:

rust
fn main() {
    let mut prices = vec![15000, 17500, 20000];

    for price in prices.iter_mut() {
        *price = *price - 1000;
    }

    println!("Harga setelah promosi: {:?}", prices);
}

Output yang Dihasilkan:

text
Harga setelah promosi: [14000, 16500, 19000]

Uraian baris-per-barisnya:

  1. Baris 2 (let mut prices = vec![15000, 17500, 20000];): koleksi harus dideklarasikan mut karena isinya akan diubah. Tanpa mut, kompilator menolak pembuatan iterator iter_mut() sejak awal.
  2. Baris 4 (for price in prices.iter_mut()): iterator meminjam koleksi secara mutable, satu elemen pada satu waktu, dan menyerahkan &mut i32 per putaran. Sesuai aturan dari section Ownership, hanya satu pinjaman mutable yang aktif dalam satu momen — iterator iter_mut() adalah satu-satunya pemegang pinjaman itu selama perulangan berlangsung.
  3. Baris 5 (*price = *price - 1000;): operator * melakukan dereference — membaca nilai yang ditunjuk reference (*price di sisi kanan), menguranginya 1000, lalu menulis hasilnya kembali ke lokasi yang sama (*price di sisi kiri). Koleksi diubah tanpa penyalinan dan tanpa pemindahan.
  4. Baris 8 (println!(..., prices)): pinjaman mutable berakhir bersama perulangan, jadi prices dapat dibaca kembali — dan output membuktikan ketiga harga benar berubah.

Bentuk Singkat for dan Desugaring #

Ketiga method tersebut punya padanan sintaksis yang lebih ringkas, dan Rust mengatur pemetaannya lewat trait IntoIterator:

  • for x in &collection setara for x in collection.iter() — pinjam baca.
  • for x in &mut collection setara for x in collection.iter_mut() — pinjam ubah.
  • for x in collection setara for x in collection.into_iter() — pindahkan.

Bentuk singkat ini bukan sihir. Kompilator menerjemahkan for menjadi urutan panggilan next — penulisan manual berikut menghasilkan perilaku yang identik dengan for name in menu.iter():

rust
fn main() {
    let menu = vec!["Kopi Gayo", "Teh Melati"];

    let mut iterator = menu.iter();

    while let Some(name) = iterator.next() {
        println!("Menu: {}", name);
    }
}

Output yang Dihasilkan:

text
Menu: Kopi Gayo
Menu: Teh Melati

Mari kita bedah terjemahan manualnya:

  1. Baris 4 (let mut iterator = menu.iter();): iterator dibuat dan disimpan di variabel mut — langkah yang biasanya disembunyikan oleh for.
  2. Baris 6 (while let Some(name) = iterator.next()): setiap putaran memanggil next, lalu pola Some(name) membedah Option. Selama hasilnya Some, badan perulangan berjalan dan name terikat pada nilai di dalamnya. Ketika next mengembalikan None, pola gagal dicocokkan dan while berhenti — persis perilaku driver yang kita uraikan di chapter sebelumnya.
  3. Baris 7 (println!("Menu: {}", name);): badan perulangan bekerja normal, tanpa menyadari bahwa bentuk aslinya adalah perulangan for.

Ringkasan #

Tiga bentuk iterasi Vec memetakan langsung ke tiga model akses data: iter() menyerahkan &T dan membiarkan koleksi utuh, iter_mut() menyerahkan &mut T untuk mengubah elemen di tempat, dan into_iter() memindahkan kepemilikan elemen ke perulangan hingga koleksi asli tidak dapat dipakai lagi — pelanggarannya ditangkap sebagai E0382. Bentuk singkat for x in &v, for x in &mut v, dan for x in v adalah gula sintaksis ketiganya, yang di balik layar diterjemahkan menjadi pemanggilan next berulang sampai None. Satu sifat penting ketiga bentuk ini belum kita bahas: iterator sebenarnya tidak melakukan pekerjaan apa pun sampai benar-benar dipaksa. Sifat malas inilah yang menjadi pokok chapter berikutnya.