Koleksi data ada untuk ditelusuri. Guru ingin mencetak seluruh nilai ujian satu per satu, kasir ingin memeriksa setiap item di keranjang, dan aplikasi tugas harian ingin memproses daftar pekerjaan sampai tuntas. Menulis satu println! per elemen tidak mungkin ketika jumlahnya seribu, dan while dengan penghitung index adalah pola rawan typo index. Rust menyediakan loop for yang berjalan langsung di atas vector — dan terdapat tiga mode iterasi dengan konsekuensi ownership yang berbeda: membaca, mengubah, dan mengambil alih. Tiga mode inilah isi chapter ini, dan pemahaman mode ketiga menjadi fondasi saat kita bertemu iterator adapter di section berikutnya.
Iterasi Hanya untuk Membaca #
Mode paling umum: kita ingin melihat setiap elemen tanpa mengubah apa pun. Cukup tuliskan &nama_vector setelah kata kunci in:
fn main() {
let scores = vec![80, 95, 70, 88, 60];
println!("Daftar nilai ujian:");
for score in &scores {
println!("Nilai: {}", score);
}
println!("Jumlah nilai yang ditampilkan: {}", scores.len());
}Output yang Dihasilkan:
Daftar nilai ujian:
Nilai: 80
Nilai: 95
Nilai: 70
Nilai: 88
Nilai: 60
Jumlah nilai yang ditampilkan: 5Mari kita bedah kode di atas baris per baris:
- Baris 2 (
let scores = vec![80, 95, 70, 88, 60];): vector berisi lima nilaii32, diikat tanpamutkarena program hanya membaca isinya. - Baris 5 (
for score in &scores {):&scoresmembuat loop meminjam vector secara immutable. Pada setiap putaran, variabelscoremenerima satu elemen dalam bentuk reference&i32— bukan salinan nilai, melainkan pinjaman menuju elemen asli di heap. - Baris 6 (
println!("Nilai: {}", score);): reference&i32langsung bisa dicetak dengan{}karena operasi baca seperti ini persis kegunaan reference immutable. - Baris 9 (
scores.len()): setelah loop selesai,scorestetap hidup dan utuh. Pinjaman yang diberikan loop sudah selesai, kepemilikan tidak pernah berpindah.
Penulisan for score in &scores sebenarnya adalah bentuk ringkas dari for score in scores.iter() — keduanya menghasilkan iterator yang membagikan reference ke setiap elemen. Kita akan memakai bentuk eksplisit iter() pada mode berikutnya agar perbandingan ketiga method terlihat setara.
Satu detail pinjaman layak digarisbawahi sebelum kita lanjut. Pinjaman yang diberikan &scores hidup selama loop berjalan — selama itu juga compiler melarang operasi lain mengubah scores, karena aturan borrowing tidak mengizinkan pinjaman mutable hidup bersama pinjaman immutable. Larangan tersebut berakhir tepat ketika loop selesai, dan itulah sebabnya scores.len() pada baris terakhir kembali sah. Mekanisme ini disebut Non-Lexical Lifetimes: batas pinjaman dihitung dari pemakaian terakhir, bukan dari kurung kurawal.
Cara lama sebelum mengenal pola ini biasanya berbentuk while dengan penghitung — while i < scores.len() lalu mengakses scores[i]. Pola tersebut berfungsi, tetapi membawa dua beban: index harus dikelola manual dengan risiko typo satu angka, dan setiap akses scores[i] melewati pemeriksaan batas yang bisa panic. Loop for di atas reference menghapus kedua beban sekaligus: iterator mengetahui posisi dan batasnya sendiri, dan elemen diserahkan lewat pinjaman tanpa perhitungan index apa pun.
Iterasi dengan Mengubah Elemen #
Nilai ujian ternyata perlu dikoreksi: semua peserta mendapat bonus 5 poin. Mode pembacaan tidak bisa dipakai karena score bertipe &i32 yang terlarang diubah. Yang kita butuhkan adalah iter_mut() yang meminjam setiap elemen secara mutable:
fn main() {
let mut scores = vec![80, 95, 70, 88, 60];
for score in scores.iter_mut() {
*score += 5;
}
println!("Nilai setelah bonus 5 poin:");
for score in &scores {
println!("Nilai: {}", score);
}
}Output yang Dihasilkan:
Nilai setelah bonus 5 poin:
Nilai: 85
Nilai: 100
Nilai: 75
Nilai: 93
Nilai: 65Mari kita telaah bagian-bagian pentingnya:
- Baris 2 (
let mut scores = ...): kata kuncimutkini wajib karenaiter_mut()menuntut peminjaman mutable atasscores. - Baris 4 (
for score in scores.iter_mut() {): pada setiap putaran, variabelscoremenerima reference mutable&mut i32menuju elemen asli. Hanya satu peminjam mutable yang beredar pada satu waktu, dan iterator inilah peminjam eksklusif tersebut selama loop berjalan. - Baris 5 (
*score += 5;): tanda*melakukan dereference — mengubah nilai yang berada di ujung reference, bukan reference-nya. Tanpa*, ekspresiscore += 5akan ditolak compiler karena mencoba menambah angka ke sebuah alamat. - Baris 8-11 (loop kedua): setelah loop pertama usai, pinjaman mutable ditutup dan
scoresbisa dipinjam lagi secara immutable untuk pencetakan hasil.
Iterasi dengan Memindahkan Elemen #
Mode ketiga adalah into_iter() — singkatan yang bisa dibaca “into iteration”: elemen berpindah kepemilikan dari vector ke dalam loop, satu per satu. Bentuk ringkasnya adalah menuliskan nama vector tanpa &:
fn main() {
let tasks = vec![
String::from("Kirim email ke pemasok"),
String::from("Catat penjualan harian"),
String::from("Rapikan etalase"),
];
for task in tasks {
println!("Memproses tugas: {}", task);
}
println!("Semua tugas selesai.");
}Output yang Dihasilkan:
Memproses tugas: Kirim email ke pemasok
Memproses tugas: Catat penjualan harian
Memproses tugas: Rapikan etalase
Semua tugas selesai.Perhatikan perbedaan kunci pada variabel task: elemen diterima sebagai String utuh, bukan &String. Setiap String pindah dari dalam vector ke variabel task, dicetak, lalu di-drop di akhir putaran. Pola ini cocok untuk daftar sekali pakai — daftar tugas yang diproses hingga habis tidak perlu disimpan lagi setelah selesai. Konsekuensinya satu dan tidak bisa dinegosiasi: vector tasks menghabiskan dirinya sendiri selama loop.
Kehidupan Setelah Loop yang Memindahkan #
Mari kita buktikan klaim di atas dengan sengaja memakai tasks setelah loop selesai:
fn main() {
let tasks = vec![
String::from("Kirim email ke pemasok"),
String::from("Catat penjualan harian"),
];
for task in tasks {
println!("Memproses tugas: {}", task);
}
println!("Jumlah tugas: {}", tasks.len());
}Pesan error kompilator:
error[E0382]: borrow of moved value: `tasks`
--> main.rs:11:34
|
2 | let tasks = vec![
| ----- move occurs because `tasks` has type `Vec<String>`, which does not implement the `Copy` trait
...
7 | for task in tasks {
| ----- `tasks` moved due to this implicit call to `.into_iter()`
...
11 | println!("Jumlah tugas: {}", tasks.len());
| ^^^^^ value borrowed here after move
|
note: the `for` loop is desugared into a call to `std::iter::IntoIterator::into_iter`, which takes ownership of the receiver `self`, which moves `tasks`
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/iter/traits/collect.rs:312:17
help: consider iterating over a slice of the `Vec<String>`'s content to avoid moving into the `for` loop
|
7 | for task in &tasks {
| +
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`.Pesan E0382 mengungkap rahasia di balik sintaks for yang tampak sederhana: compiler menerjemahkan for task in tasks menjadi pemanggilan into_iter() yang mengambil kepemilikan penuh atas vector. Elemen terakhir telah di-drop bersama vector kosongnya di ujung loop, sehingga tasks.len() di baris 11 meminjam nilai yang sudah tidak dimiliki siapa pun. Compiler bahkan menyodorkan perbaikannya: ganti for task in tasks menjadi for task in &tasks jika vector masih dibutuhkan.
Perbandingan Tiga Mode Iterasi #
Ketiga mode kini bisa kita rangkum dalam satu tabel:
| Bentuk loop | score menerima | Kepemilikan elemen | Vector setelah loop |
|---|---|---|---|
for x in &v / v.iter() | &T | tetap milik vector | masih hidup, bisa dipakai |
for x in v.iter_mut() | &mut T | tetap milik vector | masih hidup, isinya berubah |
for x in v / v.into_iter() | T | pindah ke variabel loop | sudah dikonsumsi |
Aturan praktisnya sederhana: mulailah dari &v setiap kali ragu. Naik ke iter_mut() hanya ketika elemen memang harus diubah, dan turun ke into_iter() hanya ketika data memang sekali pakai — misalnya antrean pesanan yang diproses hingga habis atau daftar pesan yang dikirim lalu dibuang.
Ringkasan #
Loop for di atas vector memiliki tiga kepribadian: &v dan iter() membagikan reference baca, iter_mut() membagikan reference tulis yang eksklusif dengan dereference * untuk mengubah elemen, dan into_iter() (bentuk ringkas for x in v) memindahkan setiap elemen keluar dari vector hingga vector habis. Kesalahan memakai vector setelah mode ketiga menghasilkan E0382 lengkap dengan petunjuk perbaikan dari compiler. Ketiga mode ini berlaku juga untuk koleksi lain yang akan kita temui — dimulai dari pertanyaan baru: bagaimana kepemilikan elemen bekerja ketika vector itu sendiri berpindah tangan? Itulah chapter berikutnya.