Perulangan #
Rust memiliki tiga konstruksi perulangan — loop, while, dan for — tapi cara idiomatic Rust menggunakannya berbeda cukup jauh dari bahasa lain. loop bisa mengembalikan nilai, membuat ia berguna sebagai expression bukan sekadar statement. for tidak bekerja dengan indeks manual seperti for (int i = 0; i < n; i++) di C — ia hanya bisa mengiterasi sesuatu yang mengimplementasikan trait IntoIterator. Dan ekosistem iterator Rust jauh lebih kaya dari sekadar “loop melalui array”: ada map, filter, fold, zip, chain, dan puluhan adaptor lain yang semuanya bisa dirangkai tanpa alokasi perantara. Artikel ini membahas ketiga jenis loop, cara memilih di antara ketiganya, dan bagaimana iterator method sering bisa menggantikan loop eksplisit dengan kode yang lebih ringkas dan lebih mudah di-compose.
loop — Perulangan Tak Terbatas
#
loop menjalankan blok kode berulang tanpa batas sampai ada break yang eksplisit menghentikannya. Ini bukan anti-pattern — ada banyak situasi di mana perulangan tanpa kondisi awal justru paling tepat: server yang terus menunggu koneksi, game loop, atau retry logic.
fn main() {
let mut counter = 0;
loop {
counter += 1;
if counter == 5 {
break;
}
}
println!("Counter berhenti di: {}", counter); // 5
}
loop sebagai Expression
#
Ini fitur yang tidak ada di banyak bahasa lain: break bisa membawa nilai keluar dari loop, menjadikannya sebuah expression yang menghasilkan hasil. Berguna untuk retry logic atau komputasi yang membutuhkan perulangan sampai kondisi terpenuhi.
fn main() {
let mut upaya = 0;
// loop mengembalikan nilai via break
let hasil = loop {
upaya += 1;
// Simulasi operasi yang perlu dicoba berulang
if upaya == 3 {
break upaya * 10; // nilai ini menjadi hasil dari seluruh `loop`
}
};
println!("Berhasil setelah {} upaya, hasil: {}", upaya, hasil); // 3, 30
// Penggunaan nyata: retry dengan backoff
let koneksi = loop {
match coba_koneksi() {
Ok(conn) => break conn,
Err(e) => {
eprintln!("Gagal: {}, mencoba lagi...", e);
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
};
// `koneksi` sudah pasti Ok di sini — tidak perlu unwrap lagi
println!("Terhubung: {}", koneksi);
}
fn coba_koneksi() -> Result<String, &'static str> {
// Simulasi — anggap berhasil setelah beberapa upaya
Ok(String::from("localhost:5432"))
}
Label Loop dan break Berlabel
#
Pada nested loop, break tanpa label hanya menghentikan loop terdalam. Gunakan label ('nama:) untuk menghentikan loop luar dari dalam loop dalam:
fn main() {
// ANTI-PATTERN: menggunakan flag boolean untuk keluar dari nested loop
let mut selesai = false;
'outer_simulasi: for i in 0..5 {
for j in 0..5 {
if i == 2 && j == 3 {
selesai = true;
break;
}
}
if selesai { break; }
}
// BENAR: label loop jauh lebih bersih
'pencarian: for i in 0..5 {
for j in 0..5 {
println!("Memeriksa ({}, {})", i, j);
if i == 2 && j == 3 {
println!("Ditemukan di ({}, {})", i, j);
break 'pencarian; // keluar dari loop luar langsung
}
}
}
println!("Selesai");
// loop berlabel dengan nilai return
let mut i = 0;
let nilai = 'luar: loop {
let mut j = 0;
loop {
if i + j == 10 {
break 'luar i * j; // kembalikan nilai dari loop berlabel
}
j += 1;
}
i += 1;
};
println!("Nilai: {}", nilai);
}
while — Perulangan Berbasis Kondisi
#
while mengevaluasi kondisi sebelum setiap iterasi dan berhenti saat kondisi menjadi false. Ini pilihan tepat ketika jumlah iterasi tidak diketahui di awal dan bergantung pada state yang berubah.
fn main() {
// Hitung hingga kondisi terpenuhi
let mut n = 1;
while n < 100 {
n *= 2;
}
println!("Pertama di atas 100: {}", n); // 128
// Countdown
let mut hitungan = 5;
while hitungan > 0 {
print!("{}... ", hitungan);
hitungan -= 1;
}
println!("Liftoff!");
}
Kapan while Lebih Tepat dari loop
#
fn main() {
// ANTI-PATTERN: while diemulasi dengan loop + if + break
let mut x = 0;
loop {
if x >= 10 { break; }
x += 1;
}
// BENAR: while langsung lebih jelas niatnya
let mut x = 0;
while x < 10 {
x += 1;
}
println!("x = {}", x);
// ANTI-PATTERN: while digunakan untuk iterasi koleksi dengan indeks manual
let data = vec![10, 20, 30, 40, 50];
let mut idx = 0;
while idx < data.len() {
println!("{}", data[idx]);
idx += 1;
// Mudah lupa increment, risiko off-by-one, risiko index out of bounds
}
// BENAR: for jauh lebih aman dan idiomatis untuk iterasi koleksi
for elemen in &data {
println!("{}", elemen);
}
}
while dengan Input Pengguna
#
Pola umum while adalah terus meminta input sampai kondisi valid terpenuhi:
use std::io;
fn baca_angka_positif() -> u32 {
loop {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Gagal membaca");
match input.trim().parse::<u32>() {
Ok(n) if n > 0 => return n,
Ok(_) => println!("Harus lebih dari 0, coba lagi:"),
Err(_) => println!("Bukan angka valid, coba lagi:"),
}
}
}
for — Iterasi atas Koleksi
#
for adalah loop yang paling sering digunakan di Rust. Ia bekerja dengan tipe apapun yang mengimplementasikan trait IntoIterator — array, Vec, Range, String, HashMap, dan tipe apapun yang kamu definisikan sendiri.
fn main() {
// Array
let buah = ["apel", "mangga", "jeruk", "pisang"];
for b in buah {
print!("{} ", b);
}
println!();
// Vec
let angka = vec![1, 2, 3, 4, 5];
for n in &angka { // & agar angka tidak di-move
print!("{} ", n);
}
println!();
// Range eksklusif
for i in 0..5 {
print!("{} ", i); // 0 1 2 3 4
}
println!();
// Range inklusif
for i in 1..=5 {
print!("{} ", i); // 1 2 3 4 5
}
println!();
// Range terbalik dengan .rev()
for i in (1..=5).rev() {
print!("{} ", i); // 5 4 3 2 1
}
println!();
}
Tiga Mode Iterasi Vec #
Cara kamu menulis for menentukan apakah elemen di-move, dipinjam, atau dipinjam secara mutable:
fn main() {
let v = vec![String::from("a"), String::from("b"), String::from("c")];
// 1. Move — ownership berpindah, v tidak bisa digunakan setelah ini
for s in v {
println!("{}", s);
}
// println!("{:?}", v); // error: v sudah di-move
let v = vec![String::from("a"), String::from("b"), String::from("c")];
// 2. Immutable borrow — v tetap valid, tidak bisa modifikasi elemen
for s in &v {
println!("{}", s); // s bertipe &String
}
println!("v masih ada: {:?}", v); // ✓
let mut v = vec![1, 2, 3, 4, 5];
// 3. Mutable borrow — bisa modifikasi setiap elemen
for n in &mut v {
*n *= 2; // harus deref untuk mengubah nilai
}
println!("v setelah dikali 2: {:?}", v); // [2, 4, 6, 8, 10]
}
enumerate — Indeks dan Nilai Sekaligus
#
fn main() {
let makanan = ["nasi", "ayam", "sayur", "tempe"];
// enumerate() menghasilkan pasangan (indeks, &elemen)
for (i, item) in makanan.iter().enumerate() {
println!("{}. {}", i + 1, item);
}
// 1. nasi
// 2. ayam
// 3. sayur
// 4. tempe
// ANTI-PATTERN: menggunakan indeks manual untuk mendapat indeks dan nilai
let mut idx = 0;
for item in &makanan {
println!("{}. {}", idx + 1, item);
idx += 1;
}
// Lebih verbose, risiko lupa increment atau off-by-one
}
zip — Iterasi Dua Koleksi Bersamaan
#
fn main() {
let nama = ["Budi", "Sari", "Joko"];
let nilai = [85, 92, 78];
// zip menggabungkan dua iterator menjadi iterator pasangan
for (n, v) in nama.iter().zip(nilai.iter()) {
println!("{}: {}", n, v);
}
// zip berhenti saat iterator yang lebih pendek habis
let a = [1, 2, 3, 4, 5];
let b = [10, 20, 30]; // lebih pendek
for (x, y) in a.iter().zip(b.iter()) {
println!("{} + {} = {}", x, y, x + y);
}
// Hanya 3 iterasi — (1,10), (2,20), (3,30)
}
break dan continue
#
break menghentikan loop, continue melewati sisa iterasi saat ini dan lanjut ke iterasi berikutnya. Keduanya bisa digunakan di loop, while, dan for.
fn main() {
// break — hentikan saat elemen ditemukan
let data = [3, 7, 2, 9, 1, 8, 5];
let target = 9;
let mut posisi = None;
for (i, &val) in data.iter().enumerate() {
if val == target {
posisi = Some(i);
break; // tidak perlu lanjut
}
}
println!("Target {} di posisi: {:?}", target, posisi);
// continue — lewati elemen yang tidak memenuhi syarat
println!("Angka ganjil:");
for n in 0..10 {
if n % 2 == 0 {
continue; // lewati genap
}
print!("{} ", n);
}
println!();
// continue berlabel — lewati iterasi di loop luar
'baris: for baris in 0..4 {
for kolom in 0..4 {
if kolom == 2 {
continue 'baris; // lanjut ke baris berikutnya
}
print!("({},{}) ", baris, kolom);
}
println!(); // tidak tercapai karena continue 'baris melompatinya
}
println!();
}
Iterator Adaptor — Alternatif Loop yang Lebih Expressif #
Salah satu kekuatan terbesar Rust adalah ekosistem iterator-nya. Alih-alih menulis loop eksplisit, kamu bisa merangkai adaptor — map, filter, fold, take, skip, dan puluhan lainnya. Keuntungannya: lebih ringkas, lebih mudah dikomposisi, dan seringkali lebih mudah dibaca niatnya.
flowchart LR
S[Sumber\nVec / Array / Range] --> A1[map\ntransformasi]
A1 --> A2[filter\npenyaringan]
A2 --> A3[take / skip\npembatasan]
A3 --> C[Konsumer\ncollect / sum / for_each]
C --> R[Hasil Akhir]map — Transformasi Setiap Elemen
#
fn main() {
let angka = vec![1, 2, 3, 4, 5];
// ANTI-PATTERN: loop eksplisit untuk transformasi sederhana
let mut kuadrat = Vec::new();
for &n in &angka {
kuadrat.push(n * n);
}
// BENAR: map lebih deklaratif — menjelaskan *apa*, bukan *bagaimana*
let kuadrat: Vec<i32> = angka.iter()
.map(|&n| n * n)
.collect();
println!("{:?}", kuadrat); // [1, 4, 9, 16, 25]
// Rangkai beberapa transformasi
let hasil: Vec<String> = (1..=5)
.map(|n| n * n)
.map(|n| format!("{}²", n))
.collect();
println!("{:?}", hasil); // ["1²", "4²", "9²", "16²", "25²"]
}
filter — Penyaringan Berdasarkan Kondisi
#
fn main() {
let angka = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Saring hanya yang genap
let genap: Vec<&i32> = angka.iter()
.filter(|&&n| n % 2 == 0)
.collect();
println!("Genap: {:?}", genap); // [2, 4, 6, 8, 10]
// filter + map = filter_map (lebih efisien)
let kata = vec!["42", "halo", "7", "dunia", "100"];
let angka_valid: Vec<u32> = kata.iter()
.filter_map(|s| s.parse::<u32>().ok()) // parse dan saring sekaligus
.collect();
println!("Angka valid: {:?}", angka_valid); // [42, 7, 100]
}
fold — Reduksi ke Satu Nilai
#
fn main() {
let angka = vec![1, 2, 3, 4, 5];
// sum() adalah singkatan dari fold untuk penjumlahan
let jumlah: i32 = angka.iter().sum();
println!("Jumlah: {}", jumlah); // 15
// product() untuk perkalian
let hasil: i32 = angka.iter().product();
println!("Produk: {}", hasil); // 120
// fold() untuk reduksi arbitrer
let maks = angka.iter().fold(i32::MIN, |acc, &x| acc.max(x));
println!("Maksimum: {}", maks); // 5
// Bangun String dari koleksi
let kalimat = ["Rust", "adalah", "bahasa", "yang", "cepat"];
let gabung = kalimat.iter().fold(String::new(), |mut acc, &kata| {
if !acc.is_empty() { acc.push(' '); }
acc.push_str(kata);
acc
});
println!("{}", gabung); // Rust adalah bahasa yang cepat
// join() lebih idiomatis untuk kasus di atas
let gabung2 = kalimat.join(" ");
println!("{}", gabung2);
}
Adaptor Lain yang Sering Digunakan #
fn main() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// take — ambil n elemen pertama
let lima_pertama: Vec<_> = data.iter().take(5).collect();
println!("5 pertama: {:?}", lima_pertama);
// skip — lewati n elemen pertama
let setelah_lima: Vec<_> = data.iter().skip(5).collect();
println!("Setelah 5: {:?}", setelah_lima);
// take_while — ambil selama kondisi terpenuhi
let kecil: Vec<_> = data.iter().take_while(|&&x| x < 5).collect();
println!("Kurang dari 5: {:?}", kecil);
// chain — gabungkan dua iterator
let a = [1, 2, 3];
let b = [4, 5, 6];
let semua: Vec<_> = a.iter().chain(b.iter()).collect();
println!("Chain: {:?}", semua);
// any dan all — cek kondisi
println!("Ada yang > 8: {}", data.iter().any(|&x| x > 8)); // true
println!("Semua > 0: {}", data.iter().all(|&x| x > 0)); // true
// count — hitung elemen
let jumlah_genap = data.iter().filter(|&&x| x % 2 == 0).count();
println!("Jumlah genap: {}", jumlah_genap); // 5
// find — cari elemen pertama yang memenuhi kondisi
let pertama_genap = data.iter().find(|&&x| x % 2 == 0);
println!("Genap pertama: {:?}", pertama_genap); // Some(2)
// position — cari indeks elemen pertama
let posisi = data.iter().position(|&x| x == 7);
println!("Posisi 7: {:?}", posisi); // Some(6)
// min dan max
println!("Min: {:?}", data.iter().min()); // Some(1)
println!("Max: {:?}", data.iter().max()); // Some(10)
}
Iterator Kustom #
Kamu bisa membuat tipe apapun menjadi iterable dengan mengimplementasikan trait Iterator. Satu-satunya method yang wajib diimplementasikan adalah next() — semua adaptor (map, filter, fold, dll.) tersedia secara otomatis setelah itu.
// Iterator Fibonacci yang menghasilkan nilai tanpa batas
struct Fibonacci {
curr: u64,
next: u64,
}
impl Fibonacci {
fn baru() -> Self {
Fibonacci { curr: 0, next: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let hasil = self.curr;
let baru_next = self.curr + self.next;
self.curr = self.next;
self.next = baru_next;
Some(hasil) // tidak pernah None — infinite iterator
}
}
fn main() {
// Ambil 10 bilangan Fibonacci pertama
let fib_10: Vec<u64> = Fibonacci::baru()
.take(10)
.collect();
println!("{:?}", fib_10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// Filter hanya yang genap, ambil 5
let fib_genap: Vec<u64> = Fibonacci::baru()
.filter(|n| n % 2 == 0)
.take(5)
.collect();
println!("{:?}", fib_genap); // [0, 2, 8, 34, 144]
// Jumlah Fibonacci di bawah 100
let jumlah: u64 = Fibonacci::baru()
.take_while(|&n| n < 100)
.sum();
println!("Jumlah Fibonacci < 100: {}", jumlah); // 232
}
Kapan Memilih Jenis Loop #
Gunakan loop jika:
✓ Tidak ada kondisi awal yang jelas — retry logic, server event loop
✓ Perlu mengembalikan nilai dari perulangan via break
✓ Perulangan bisa berhenti dari banyak titik berbeda
Gunakan while jika:
✓ Ada kondisi boolean yang diperiksa di awal setiap iterasi
✓ Jumlah iterasi tidak diketahui dan bergantung pada state eksternal
✓ Membaca input sampai kondisi valid terpenuhi
Gunakan for jika:
✓ Mengiterasi koleksi: Vec, array, HashMap, Range
✓ Jumlah iterasi sudah diketahui atau terbatas oleh ukuran koleksi
✓ Ini kasus paling umum — gunakan for sebagai default
Gunakan iterator adaptor (map/filter/fold) jika:
✓ Transformasi atau reduksi sederhana pada koleksi
✓ Komposisi beberapa operasi sekaligus
✓ Hasil harus dikumpulkan ke Vec atau tipe lain
Ringkasan #
loopadalah expression —break nilaimengembalikan nilai dari seluruh blokloop, berguna untuk retry logic dan komputasi hingga kondisi terpenuhi.- Label loop untuk nested loop —
break 'labeldancontinue 'labelmenghentikan atau melanjutkan loop yang spesifik, jauh lebih bersih dari flag boolean.fordengan&koleksiuntuk borrow —for x in &vmeminjam v,for x in vmemindahkan ownership,for x in &mut vmeminjam secara mutable.whileuntuk kondisi berbasis state — gunakan ketika jumlah iterasi tidak diketahui dan bergantung pada kondisi yang berubah di runtime.- Iterasi koleksi dengan
for, bukanwhile + indeks— lebih aman (tidak ada risiko index out of bounds), lebih ringkas, dan lebih idiomatis.enumerate()untuk indeks + nilai,zip()untuk dua koleksi bersamaan — hindari manajemen indeks manual.- Iterator adaptor untuk transformasi deklaratif —
map,filter,fold,take,skipmengekspresikan apa yang dilakukan bukan bagaimana, dan bisa dirangkai tanpa alokasi perantara.filter_maplebih efisien darifilter+map— satu langkah untuk menyaring dan mentransformasi sekaligus.- Iterator kustom cukup implementasikan
next()— semua adaptor tersedia gratis via traitIteratorsetelah methodnext()diimplementasikan.