Collections, Iterators, dan Closures di Rust: Panduan Praktis • Erlkim

Collections, Iterators, dan Closures di Rust: Panduan Praktis

Collections, Iterators, dan Closures di Rust: Panduan Praktis
Jun 14, 2026 ... views
13 min

Di artikel sebelumnya kita sudah belajar concurrency dan async programming. Sekarang saatnya belajar tiga konsep yang akan kamu pakai setiap hari saat menulis Rust: collections, iterators, dan closures.

Ketiga konsep ini saling berkaitan erat. Collections menyimpan data, closures mendefinisikan operasi, dan iterators menghubungkan keduanya dengan cara yang elegan dan efisien.

Collections

Collections adalah tipe data yang menyimpan kumpulan nilai. Berbeda dari array yang ukurannya tetap, collections bisa berubah ukurannya. Ada tiga collections utama di Rust:

  • Vec: List yang terurut, bisa bertambah ukurannya
  • HashMap: Pasangan key-value, akses cepat berdasarkan key
  • String: Teks yang bisa dimodifikasi

Vec (Vector)

Vec adalah collection yang paling sering digunakan. Mirip seperti array dinamis di bahasa lain.

Membuat Vec

fn main() {
    // Cara 1: Vec::new()
    let mut v1: Vec<i32> = Vec::new();
    v1.push(1);
    v1.push(2);
    v1.push(3);

    // Cara 2: vec! macro
    let v2 = vec![1, 2, 3, 4, 5];

    // Cara 3: dengan kapasitas
    let mut v3 = Vec::with_capacity(100);
    for i in 0..100 {
        v3.push(i); // Tidak perlu realokasi
    }

    println!("v1: {:?}", v1);
    println!("v2: {:?}", v2);
    println!("v3 length: {}", v3.len());
}

Vec::with_capacity() lebih efisien jika kamu tahu berapa banyak elemen yang akan disimpan.

Mengakses Elemen

fn main() {
    let v = vec![10, 20, 30, 40, 50];

    // Cara 1: indexing (panic jika out of bounds)
    let third = v[2];
    println!("Elemen ke-3: {}", third);

    // Cara 2: get() (mengembalikan Option)
    match v.get(2) {
        Some(value) => println!("Elemen ke-3: {}", value),
        None => println!("Elemen tidak ada"),
    }

    // Akses terakhir
    let last = v.last().unwrap();
    println!("Terakhir: {}", last);

    // Cek apakah kosong
    println!("Kosong? {}", v.is_empty());

    // Panjang
    println!("Panjang: {}", v.len());
}

Gunakan get() jika index mungkin tidak valid. Gunakan [] jika kamu yakin index valid.

Memodifikasi Vec

fn main() {
    let mut v = vec![1, 2, 3];

    // Tambah di akhir
    v.push(4);
    v.push(5);

    // Hapus dari akhir
    let last = v.pop(); // Some(5)

    // Sisip di posisi tertentu
    v.insert(1, 10); // Sisip 10 di index 1

    // Hapus di posisi tertentu
    v.remove(1); // Hapus elemen di index 1

    // Hapus semua elemen tertentu
    v.retain(|&x| x > 2); // Sisakan yang > 2

    // Clear semua
    // v.clear();

    println!("{:?}", v); // [3, 4]
}

Vec dengan Berbagai Tipe (menggunakan Enum)

#[derive(Debug)]
enum CellValue {
    Int(i32),
    Float(f64),
    Text(String),
}

fn main() {
    let row = vec![
        CellValue::Int(1),
        CellValue::Text(String::from("ERLKIM")),
        CellValue::Float(3.14),
    ];

    for cell in &row {
        match cell {
            CellValue::Int(n) => println!("Integer: {}", n),
            CellValue::Float(f) => println!("Float: {}", f),
            CellValue::Text(s) => println!("Text: {}", s),
        }
    }
}

HashMap

HashMap menyimpan data dalam pasangan key-value. Akses data sangat cepat berdasarkan key.

Membuat HashMap

use std::collections::HashMap;

fn main() {
    // Cara 1: HashMap::new()
    let mut scores = HashMap::new();
    scores.insert(String::from("ERLKIM"), 95);
    scores.insert(String::from("Guest"), 80);

    // Cara 2: dari iterator
    let names = vec!["Alice", "Bob", "Charlie"];
    let ages = vec![25, 30, 35];
    let people: HashMap<_, _> = names.into_iter().zip(ages.into_iter()).collect();

    println!("Scores: {:?}", scores);
    println!("People: {:?}", people);
}

zip() menggabungkan dua iterator menjadi pasangan. collect() mengumpulkan hasilnya ke dalam HashMap.

Mengakses dan Memodifikasi

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("ERLKIM"), 95);

    // Akses dengan get
    let score = scores.get("ERLKIM");
    match score {
        Some(s) => println!("Score: {}", s),
        None => println!("Tidak ditemukan"),
    }

    // Update: overwrite
    scores.insert(String::from("ERLKIM"), 100);

    // Update: hanya jika belum ada
    scores.entry(String::from("Guest")).or_insert(80);

    // Update: berdasarkan nilai lama
    let text = "hello world hello rust hello world";
    let mut word_count = HashMap::new();
    for word in text.split_whitespace() {
        let count = word_count.entry(word).or_insert(0);
        *count += 1;
    }
    println!("Word count: {:?}", word_count);

    // Hapus
    scores.remove("Guest");

    // Cek key
    if scores.contains_key("ERLKIM") {
        println!("ERLKIM ada di scores");
    }

    // Iterasi
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
}

Method entry().or_insert() sangat powerful. Jika key belum ada, masukkan default value. Jika sudah ada, kembalikan reference ke value yang ada.

Word Count - Contoh Klasik

use std::collections::HashMap;

fn word_count(text: &str) -> HashMap<&str, usize> {
    let mut count = HashMap::new();
    for word in text.split_whitespace() {
        *count.entry(word).or_insert(0) += 1;
    }
    count
}

fn main() {
    let text = "the quick brown fox jumps over the lazy dog the fox";
    let count = word_count(text);

    let mut sorted: Vec<_> = count.iter().collect();
    sorted.sort_by(|a, b| b.1.cmp(a.1));

    for (word, freq) in sorted {
        println!("{}: {}", word, freq);
    }
}

Output:

the: 3
fox: 2
quick: 1
brown: 1
jumps: 1
over: 1
lazy: 1
dog: 1

Closures

Closure adalah anonymous function yang bisa menangkap variabel dari lingkungan sekitarnya. Closures sangat umum digunakan di Rust, terutama bersama iterators.

Syntax Dasar

fn main() {
    // Function biasa
    fn add(a: i32, b: i32) -> i32 { a + b }

    // Closure
    let add_closure = |a: i32, b: i32| -> i32 { a + b };

    // Closure tanpa tipe (compiler infer)
    let add_short = |a, b| a + b;

    println!("{}", add(1, 2));
    println!("{}", add_closure(1, 2));
    println!("{}", add_short(1, 2));
}

Perbedaan utama dari function: closure bisa menangkap variabel dari luar.

Menangkap Variabel

fn main() {
    let multiplier = 3;

    // Closure menangkap multiplier
    let multiply = |x| x * multiplier;

    println!("5 x {} = {}", multiplier, multiply(5));
    println!("10 x {} = {}", multiplier, multiply(10));
}

Closures menangkap variabel dengan tiga cara:

fn main() {
    let name = String::from("ERLKIM");

    // 1. Borrow (immutable)
    let greet = || println!("Halo, {}!", name);
    greet();
    greet();
    println!("Masih bisa: {}", name); // OK

    // 2. Borrow mutable
    let mut count = 0;
    let mut increment = || {
        count += 1;
        println!("Count: {}", count);
    };
    increment(); // Count: 1
    increment(); // Count: 2

    // 3. Move (ambil ownership)
    let data = vec![1, 2, 3];
    let process = move || {
        println!("Data: {:?}", data);
    };
    process();
    // println!("{:?}", data); // ERROR! data sudah di-move
}

Closure sebagai Parameter

fn apply<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

fn main() {
    let double = |x| x * 2;
    let square = |x| x * x;

    println!("Double 5: {}", apply(double, 5));
    println!("Square 5: {}", apply(square, 5));
}

Ada tiga trait untuk closures:

  • Fn: Membaca variabel yang ditangkap (immutable borrow)
  • FnMut: Memodifikasi variabel yang ditangkap (mutable borrow)
  • FnOnce: Mengambil ownership, bisa dipanggil sekali
fn call_fn<F: Fn()>(f: F) { f(); }
fn call_fn_mut<F: FnMut()>(mut f: F) { f(); }
fn call_fn_once<F: FnOnce()>(f: F) { f(); }

fn main() {
    let name = String::from("ERLKIM");

    // Fn: hanya baca
    let greet = || println!("Halo, {}!", name);
    call_fn(greet);
    call_fn(greet);

    // FnMut: bisa ubah
    let mut count = 0;
    let mut inc = || { count += 1; };
    call_fn_mut(&mut inc);
    call_fn_mut(&mut inc);

    // FnOnce: sekali pakai
    let consume = move || { println!("Consumed: {}", name); };
    call_fn_once(consume);
    // call_fn_once(consume); // ERROR! sudah dipanggil
}

Iterators

Iterator adalah trait yang memungkinkan kamu memproses setiap elemen dalam collection secara berurutan. Ini adalah salah satu fitur paling powerful di Rust.

Trait Iterator

// Simplified version
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

Setiap tipe yang mengimplementasikan Iterator punya method next() yang mengembalikan Some(item) atau None saat habis.

Membuat Iterator

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    // .iter() menghasilkan &T
    for n in numbers.iter() {
        print!("{} ", n);
    }
    println!();

    // .into_iter() menghasilkan T (mengambil ownership)
    for n in numbers.into_iter() {
        print!("{} ", n);
    }
    println!();
    // numbers sudah tidak valid

    let mut names = vec!["Alice", "Bob", "Charlie"];

    // .iter_mut() menghasilkan &mut T
    for name in names.iter_mut() {
        *name = "Mr. ";
    }
    println!("{:?}", names);
}

Iterator Adapters

Iterator adapters mengubah iterator menjadi iterator baru. Mereka lazy artinya tidak dieksekusi sampai di-consume.

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // map: transformasi setiap elemen
    let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
    println!("Doubled: {:?}", doubled);

    // filter: sisakan yang memenuhi syarat
    let evens: Vec<&i32> = numbers.iter().filter(|&&x| x 
## Contoh Praktis: Data Processing Pipeline

Berikut contoh nyata yang menggabungkan collections, closures, dan iterators:

```rust
use std::collections::HashMap;

#[derive(Debug)]
struct Student {
    name: String,
    grade: u32,
    score: f64,
}

fn main() {
    let students = vec![
        Student { name: "Alice".into(), grade: 10, score: 85.5 },
        Student { name: "Bob".into(), grade: 11, score: 92.0 },
        Student { name: "Charlie".into(), grade: 10, score: 78.3 },
        Student { name: "Diana".into(), grade: 11, score: 95.1 },
        Student { name: "Eve".into(), grade: 10, score: 88.7 },
        Student { name: "Frank".into(), grade: 11, score: 71.2 },
    ];

    // 1. Filter siswa dengan score > 80
    let high_scores: Vec<&Student> = students
        .iter()
        .filter(|s| s.score > 80.0)
        .collect();
    println!("Score > 80: {}", high_scores.len());

    // 2. Rata-rata score per grade
    let mut grade_scores: HashMap<u32, Vec<f64>> = HashMap::new();
    for student in &students {
        grade_scores
            .entry(student.grade)
            .or_insert_with(Vec::new)
            .push(student.score);
    }

    for (grade, scores) in &grade_scores {
        let avg: f64 = scores.iter().sum::<f64>() / scores.len() as f64;
        println!("Grade {}: avg {:.1} ({} students)", grade, avg, scores.len());
    }

    // 3. Top 3 siswa
    let mut sorted = students.iter().collect::<Vec<_>>();
    sorted.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
    let top3: Vec<&Student> = sorted.into_iter().take(3).collect();
    println!("\nTop 3:");
    for (i, s) in top3.iter().enumerate() {
        println!("{}. {} - {:.1}", i + 1, s.name, s.score);
    }

    // 4. Group names by grade
    let names_by_grade: HashMap<u32, Vec<&str>> = students
        .iter()
        .fold(HashMap::new(), |mut acc, s| {
            acc.entry(s.grade)
                .or_insert_with(Vec::new)
                .push(&s.name);
            acc
        });

    println!("\nNames by grade:");
    for (grade, names) in &names_by_grade {
        println!("Grade {}: {:?}", grade, names);
    }
}

Pipeline di atas melakukan:

  1. Filter siswa berdasarkan score
  2. Group dan hitung rata-rata per grade
  3. Sort dan take top 3
  4. Fold untuk group names by grade

Semua dilakukan dengan iterator chains yang elegan dan efisien.

Contoh: CLI Text Analyzer

use std::collections::HashMap;

fn analyze_text(text: &str) {
    // Word frequency
    let mut word_freq: HashMap<&str, usize> = HashMap::new();
    let words: Vec<&str> = text.split_whitespace().collect();
    for word in &words {
        *word_freq.entry(word).or_insert(0) += 1;
    }

    // Statistics
    let total_words = words.len();
    let unique_words = word_freq.len();
    let avg_length: f64 = words.iter().map(|w| w.len()).sum::<usize>() as f64 / total_words as f64;
    let longest = words.iter().max_by_key(|w| w.len()).unwrap();
    let shortest = words.iter().min_by_key(|w| w.len()).unwrap();

    // Character frequency
    let mut char_freq: HashMap<char, usize> = HashMap::new();
    for c in text.chars().filter(|c| !c.is_whitespace()) {
        *char_freq.entry(c).or_insert(0) += 1;
    }

    // Top 5 most frequent words
    let mut word_vec: Vec<_> = word_freq.iter().collect();
    word_vec.sort_by(|a, b| b.1.cmp(a.1));
    let top5: Vec<_> = word_vec.into_iter().take(5).collect();

    // Top 5 most frequent characters
    let mut char_vec: Vec<_> = char_freq.iter().collect();
    char_vec.sort_by(|a, b| b.1.cmp(a.1));
    let top5_chars: Vec<_> = char_vec.into_iter().take(5).collect();

    println!("=== Text Analysis ===");
    println!("Total words: {}", total_words);
    println!("Unique words: {}", unique_words);
    println!("Average word length: {:.1} chars", avg_length);
    println!("Longest word: {}", longest);
    println!("Shortest word: {}", shortest);
    println!("\nTop 5 words: {:?}", top5);
    println!("Top 5 chars: {:?}", top5_chars);
}

fn main() {
    let text = "Rust is a systems programming language Rust is fast Rust is safe Rust is concurrent";
    analyze_text(text);
}

Performance: Iterator vs Loop

Iterators di Rust sama cepatnya dengan loop biasa. Compiler mengoptimasi iterator chains menjadi kode yang sangat efisien melalui proses yang disebut zero-cost abstractions.

fn main() {
    let numbers: Vec<i32> = (1..=1000000).collect();

    // Iterator (dioptimasi oleh compiler)
    let sum1: i32 = numbers.iter().filter(|&&x| x % 2 == 0).sum();

    let mut sum2 = 0;
    for &n in &numbers {
        if n % 2 == 0 {
            sum2 += n;
        }
    }

    assert_eq!(sum1, sum2);
    println!("Sum: {}", sum1);
    println!("Kedua cara menghasilkan performa yang sama!");
}

Dalam benchmark, kedua versi biasanya menghasilkan assembly yang identik.


Tips

1. Prefer Iterator dari pada Loop

rust
rust
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();

2. Gunakan turbofish untuk type inference

rust
rust
let v = vec!["1", "2", "3"];
let numbers: Vec<i32> = v.iter().map(|s| s.parse().unwrap()).collect();

3. Hindari collect berulang

rust
rust
let result: Vec<_> = data.iter().filter(|&&x| x > 5).map(|x| x * 2).collect();

Kesimpulan

Collections, iterators, dan closures adalah trio yang sangat kuat di Rust:


Vec, HashMap, String menyimpan dan mengelola data
Closures mendefinisikan operasi secara inline dan fleksibel
Iterators memproses data dengan cara yang ekspresif dan efisien

Dengan menggabungkan ketiganya, kamu bisa menulis kode yang singkat, jelas, dan secepat loop manual. Ini adalah gaya Rust yang sesungguhnya.


Di artikel selanjutnya, kita akan belajar tentang smart pointers, macros, dan advanced pattern di Rust.

~Erlkim

Komentar

0/2000