Perulangan

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 #

  • loop adalah expressionbreak nilai mengembalikan nilai dari seluruh blok loop, berguna untuk retry logic dan komputasi hingga kondisi terpenuhi.
  • Label loop untuk nested loopbreak 'label dan continue 'label menghentikan atau melanjutkan loop yang spesifik, jauh lebih bersih dari flag boolean.
  • for dengan &koleksi untuk borrowfor x in &v meminjam v, for x in v memindahkan ownership, for x in &mut v meminjam secara mutable.
  • while untuk kondisi berbasis state — gunakan ketika jumlah iterasi tidak diketahui dan bergantung pada kondisi yang berubah di runtime.
  • Iterasi koleksi dengan for, bukan while + 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 deklaratifmap, filter, fold, take, skip mengekspresikan apa yang dilakukan bukan bagaimana, dan bisa dirangkai tanpa alokasi perantara.
  • filter_map lebih efisien dari filter + map — satu langkah untuk menyaring dan mentransformasi sekaligus.
  • Iterator kustom cukup implementasikan next() — semua adaptor tersedia gratis via trait Iterator setelah method next() diimplementasikan.

← Sebelumnya: Seleksi Kondisi   Berikutnya: Fungsi →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact