Karena pintu indeks ditutup, membaca teks dilakukan dengan berjalan menyusurinya. Untungnya berjalan menyusur teks justru pola yang paling sering kita butuhkan: memvalidasi tiap huruf, menghitung jumlah kata, atau sekadar menampilkan nama huruf demi huruf. Rust menyediakan dua lensa iterasi di pustaka standar — chars() yang berjalan per character hasil decode UTF-8, dan bytes() yang berjalan per byte mentah. Di atas keduanya berdiri satu konsep yang tidak punya API bawaan: grapheme cluster, unit teks sebagaimana dilihat manusia. Chapter ini mengajak kita berjalan dengan ketiga lensa itu sambil belajar kapan masing-masing aman dipakai.

Iterasi Character dengan chars() #

Metode chars() menghasilkan barisan character: setiap langkahnya men-decode satu code point UTF-8 menjadi nilai bertipe char. Perulangan for — yang sudah kita kuasai di Section Kontrol Alur — adalah cara paling langsung menikmatinya:

rust
fn main() {
    let name = "Résumé";
    for character in name.chars() {
        println!("[{}]", character);
    }
}

Output yang Dihasilkan:

text
[R]
[é]
[s]
[u]
[m]
[é]

Mari kita bedah kode di atas:

  1. Baris 2 (let name = "Résumé";): teks berisi 6 character — é dua kali — yang tersimpan sebagai 8 byte karena tiap é menempati 2 byte.
  2. Baris 3 (for character in name.chars() {): chars() menyiapkan barisan character; perulangan mengambil satu character per putaran dan mengikatnya ke character.
  3. Baris 4 (println!("[{}]", character);): setiap character dicetak terbungkus kurung siku agar batasnya terlihat jelas di output. Keenam baris output menegaskan bahwa é utuh keluar sebagai satu unit, tidak terbelah dua.

Lensa character juga menjadi cara paling jujur menghitung jumlah unit hasil decode — chars().count() yang kita pakai di Chapter sebelumnya bekerja dengan berjalan persis seperti perulangan di atas, hanya hasilnya diringkas menjadi satu angka. Untuk teks "Résumé" hasilnya 6, berbeda dari len() yang melaporkan 8 byte. Setiap nilai yang keluar dari chars() bertipe char — satu code point utuh, bukan sepotong byte — sehingga perulangan ini tidak pernah menyerahkan “setengah huruf” sebagaimana dicemaskan di Chapter sebelumnya.

Kalau mau, kita bisa membuktikan sendiri bahwa penghitungan character hanyalah perulangan biasa dengan penampung angka — pola yang sepenuhnya memakai bekal dari Section Kontrol Alur:

rust
fn main() {
    let name = "Budi Santoso";
    let mut count = 0;
    for _ in name.chars() {
        count = count + 1;
    }
    println!("jumlah character: {}", count);
}

Output yang Dihasilkan:

text
jumlah character: 12

Bedah singkatnya:

  1. Baris 2-3 (let name = "Budi Santoso";): teks 12 character dan penampung hitungan yang dimulai dari nol.
  2. Baris 4-5 (for _ in name.chars() {): perulangan berjalan 12 kali; nama _ sengaja dipakai karena isi tiap character tidak dibutuhkan — yang penting berapa kali langkah terjadi.
  3. Baris 7 (println!("jumlah character: {}", count);): hasil 12 — persis angka chars().count(), karena keduanya menempuh jalan yang sama.

Iterasi Byte dengan bytes() #

Lensa kedua, bytes(), meminjang teks sebagai barisan byte UTF-8 tanpa proses decode. Nilai yang dihasilkan bertipe u8 — angka 0 sampai 255 — sehingga lensa ini cocok untuk pekerjaan tingkat rendah: menghitung checksum, memeriksa header, atau memproses protokol yang bicara dalam byte:

rust
fn main() {
    let text = "Hi!";
    for byte_value in text.bytes() {
        println!("byte: {}", byte_value);
    }
}

Output yang Dihasilkan:

text
byte: 72
byte: 105
byte: 33

Bedah singkatnya:

  1. Baris 2 (let text = "Hi!";): teks ASCII murni berisi tiga byte.
  2. Baris 3 (for byte_value in text.bytes() {): bytes() menyodorkan byte demi byte mentah; byte_value bertipe u8.
  3. Baris 4 (println!("byte: {}", byte_value);): keluaran berupa angka — 72 untuk H, 105 untuk i, 33 untuk !.

Character yang Tampak Sama namun Berbeda Susunan #

Berikut sisi paling mengejutkan dari teks Unicode: dua teks bisa tampak persis sama di layar padahal susunan characternya berbeda. Huruf é dapat ditulis sebagai satu character utuh, atau dirangkai dari huruf e plus character aksen penggabung:

rust
fn main() {
    let composed = "é";
    let combined = "e\u{301}";
    println!("tampilan: {} vs {}", composed, combined);
    println!("composed: {} byte, {} character", composed.len(), composed.chars().count());
    println!("combined: {} byte, {} character", combined.len(), combined.chars().count());
}

Output yang Dihasilkan:

text
tampilan: é vs é
composed: 2 byte, 1 character
combined: 3 byte, 2 character

Mari kita telaah baris-barisnya:

  1. Baris 2 (let composed = "é";): é versi utuh — satu code point yang di-encode menjadi 2 byte.
  2. Baris 3 (let combined = "e\u{301}";): versi rangkai — huruf e diikuti character penggabung aksen \u{301} yang menempel di huruf sebelumnya. Penulisan \u{301} adalah escape sequence Unicode yang akan kita bahas di Chapter Escape dan Raw String.
  3. Baris 4 (println!("tampilan: {} vs {}", composed, combined);): kedua teks tampil identik — é vs é — karena renderer menampilkan keduanya dengan bentuk visual yang sama.
  4. Baris 5-6 (println!("composed: ...")): versi utuh berisi 2 byte dan 1 character; versi rangkai berisi 3 byte dan 2 character. Mata tidak bisa membedakannya, iterator chars() bisa.

Fenomena ini menjelaskan mengapa dua nama yang tampak sama bisa gagal dibandingkan dengan == — dan mengapa penghitungan character pun belum tentu mencerminkan persepsi manusia.

Grapheme Cluster dan Keterbatasan Pustaka Standar #

Unit yang paling mendekati persepsi manusia disebut grapheme cluster: rangkaian satu atau lebih character yang membentuk satu kesatuan visual. Emoji keluarga 👨‍👩‍👧‍👦 dari Chapter sebelumnya adalah contoh ekstremnya — 7 character dan 25 byte yang manusia lihat sebagai satu kesatuan. Versi rangkai é di atas adalah contoh yang lebih halus: 2 character yang dilihat sebagai satu grapheme cluster.

Pustaka standar Rust sengaja tidak menyediakan iterator grapheme cluster. Alasannya pragmatis: menentukan batas grapheme menuntut tabel data Unicode yang besar dan aturan lokal yang berkembang, sehingga keputusannya diserahkan ke ekosistem. Kebutuhan nyata biasanya dilayani crate eksternal seperti unicode-segmentation yang menyediakan pemisahan grapheme siap pakai — kita cukup menyebut namanya sebagai petunjuk arah, tanpa perlu memasang dependensi apa pun untuk materi Section ini.

Ringkasan #

Lensa iterasi menuntaskan masalah yang ditinggalkan larangan indeks: chars() berjalan per character hasil decode UTF-8 dan cocok untuk logika huruf, bytes() berjalan per byte mentah untuk pekerjaan encoding, dan keduanya digerakkan perulangan for yang sudah akrab. Dua teks yang tampak sama bisa berbeda susunan character — é utuh versus e plus aksen penggabung — membuktikan bahwa bahkan character bukan unit tampilan. Satu grapheme cluster bisa mencakup banyak character, dan pemisahannya menuntut crate eksternal karena pustaka standar tidak menyediakannya. Dengan ketiga lensa di tangan, kita siap memotong dan menggabungkan teks secara aman di Chapter berikutnya.