Mengambil potongan teks dan merangkai potongan-potongan itu kembali adalah dua pekerjaan teks yang paling sering muncul: memisahkan nama depan dari nama belakang, memecah baris data CSV, atau menggabungkan daftar hari menjadi satu kalimat. Kompilator sudah mengisyaratkan alatnya di Chapter sebelumnya — “string indices are ranges of usize” — dan sekarang kita buka kotaknya. Chapter ini memuat tiga kelompok perkakas: slice rentang byte untuk memotong, split untuk memecah menurut pemisah, serta join, concat, dan repeat untuk menyusun kembali. Ketiganya bekerja di atas representasi UTF-8, sehingga satu aturan keselamatan akan menyertainya: batas potongan harus jatuh di antara character.

Memotong dengan Slice Rentang Byte #

Chapter Slice dan Referensi memperkenalkan slice sebagai pinjaman sepotong data. Untuk teks, sintaksnya adalah &teks[mulai..akhir] dengan satuan indeks byte — bukan character. Slice meminjam: tidak ada penyalinan teks, hanya penunjuk ke posisi awal plus panjang potongan. Bentuk &teks[..akhir] dan &teks[mulai..] juga sah untuk potongan dari awal atau sampai akhir:

rust
fn main() {
    let sentence = "Belajar Rust itu menyenangkan";
    let subject = &sentence[0..7];
    let verb = &sentence[8..12];
    let rest = &sentence[13..];
    println!("subject: {}", subject);
    println!("verb: {}", verb);
    println!("rest: {}", rest);
}

Output yang Dihasilkan:

text
subject: Belajar
verb: Rust
rest: itu menyenangkan

Mari kita bedah kode di atas baris per baris:

  1. Baris 2 (let sentence = "Belajar Rust itu menyenangkan";): teks acuan ASCII murni dengan 29 byte; satu byte satu character, sehingga menghitung indeksnya mudah.
  2. Baris 3 (let subject = &sentence[0..7];): pinjaman atas byte 0 sampai sebelum 7 — kata Belajar. Indeks akhir bersifat eksklusif: byte ke-7 (spasi) tidak ikut.
  3. Baris 4 (let verb = &sentence[8..12];): potongan byte 8 sampai 11 — kata Rust. Karena polanya konsisten, indeks ini bisa dihitung dari posisi tiap kata.
  4. Baris 5 (let rest = &sentence[13..];): rentang terbuka di kanan mengambil dari byte 13 sampai akhir teks — itu menyenangkan.
  5. Baris 6-8 (println! …): ketiga slice dicetak. Semua slice menunjuk ke memori teks yang sama; tidak ada String baru yang dibuat.

Batas Character dan Bahaya Potongan di Tengah Huruf #

Aturan keselamatannya tegas: kedua ujung rentang harus jatuh di char boundary — posisi byte tempat sebuah character bermula atau berakhir. Teks ASCII selalu aman karena semua byte adalah batas. Teks non-ASCII tidak: huruf Rusia П menempati byte 0..2, sehingga memotong di byte 1 berarti mencacah huruf itu di tengah. Rust menolak menebak dan memilih berhenti:

rust
fn main() {
    let greeting = "Привет";
    let first_letter = &greeting[0..1];
    println!("huruf pertama: {}", first_letter);
}

Output yang Dihasilkan:

text
thread 'main' (16387) panicked at main.rs:3:33:
end byte index 1 is not a char boundary; it is inside 'П' (bytes 0..2 of string)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Bedah kejadiannya:

  1. Baris 2 (let greeting = "Привет";): teks berisi 6 huruf Rusia yang masing-masing 2 byte — byte 0..2 milik huruf П.
  2. Baris 3 (let first_letter = &greeting[0..1];): rentang meminta byte 0 saja, padahal huruf pertama belum selesai di byte 1. Program berhenti dengan panic pada saat baris ini dieksekusi; baris 4 tidak pernah tercapai.
  3. Pesan panic: teks pesannya sangat deskriptif — indeks akhir 1 bukan batas character, berada di dalam huruf П yang menempati byte 0 sampai 2. Angka (16387) adalah penanda thread internal yang nilainya berubah setiap eksekusi, jadi angka di mesin kita akan berbeda. Program keluar dengan kode 101, penanda panic.

Pintu amannya adalah get(rentang): metode ini memeriksa keabsahan rentang dan mengembalikan nilai bertipe Option — Some berisi potongan bila rentang sah, None bila tidak. Pola match dari Section Kontrol Alur menangani kedua kemungkinannya:

rust
fn main() {
    let file_name = "laporan_q1.pdf";
    match file_name.get(0..7) {
        Some(chunk) => println!("awalan: {}", chunk),
        None => println!("rentang byte tidak valid"),
    }
    match file_name.get(0..100) {
        Some(chunk) => println!("awalan: {}", chunk),
        None => println!("rentang byte tidak valid"),
    }
}

Output yang Dihasilkan:

text
awalan: laporan
rentang byte tidak valid

Bedah singkatnya:

  1. Baris 3 (match file_name.get(0..7) {): rentang 0..7 sah pada teks 14 byte dan jatuh di antara kata, sehingga cabang Some(chunk) berjalan dan mencetak laporan.
  2. Baris 7 (match file_name.get(0..100) {): rentang melebihi panjang teks sehingga tidak sah — get memilih jalan None alih-alih membuat program panic. Tipe Option ini akan dibahas tuntas di Section Struktur Data; untuk sekarang, match dengan dua cabang Some/None sudah cukup dipakai sebagai pola.

Memecah Teks dengan split dan split_whitespace #

Untuk memotong menurut pemisah, split(pemisah) memecah teks setiap kali bertemu separator lalu menyodorkan potongan-potongannya satu per satu — dan pola for menyambutnya dengan natural. Varian split_whitespace() memecah menurut spasi berturut-turut dan tab sekaligus, sambil membuang potongan kosong:

rust
fn main() {
    let csv = "Budi,Jakarta,1990";
    for item in csv.split(',') {
        println!("- {}", item);
    }
    let sentence = "Belajar   Rust  itu menyenangkan";
    for word in sentence.split_whitespace() {
        println!("kata: {}", word);
    }
}

Output yang Dihasilkan:

text
- Budi
- Jakarta
- 1990
kata: Belajar
kata: Rust
kata: itu
kata: menyenangkan

Mari kita telaah baris-barisnya:

  1. Baris 2 (let csv = "Budi,Jakarta,1990";): satu baris data dengan koma sebagai pemisah.
  2. Baris 3 (for item in csv.split(',') {): split(',') memecah teks setiap koma; perulangan menerima tiga potongan: Budi, Jakarta, dan 1990.
  3. Baris 7 (let sentence = "Belajar Rust itu menyenangkan";): kalimat dengan spasi tak beraturan — tiga spasi di satu tempat, dua di tempat lain.
  4. Baris 8 (for word in sentence.split_whitespace() {): split_whitespace() memperlakukan segala rentasan spasi sebagai satu pemisah, sehingga keluarannya bersih: empat kata tanpa potongan kosong. split(' ') polos akan menghasilkan potongan kosong di antara spasi berturut-turut.

Menggabungkan dengan join, concat, dan repeat #

Arah sebaliknya — merakit potongan menjadi teks utuh — dilayani tiga metode. join(pemisah) menjahit elemen-elemen teks dengan pemisah di sela-selanya; concat() menjahit tanpa pemisah; repeat(n) menggandakan teks sebanyak n kali. Ketiganya mengembalikan String milik sendiri — hasil penggabungan dialokasikan di heap:

rust
fn main() {
    let days = ["Senin", "Rabu", "Jumat"];
    let joined = days.join(", ");
    println!("join: {}", joined);
    let parts = ["rust", "dan", "teks"];
    println!("concat: {}", parts.concat());
    let line = "-".repeat(20);
    println!("{}| batas |{}", line, line);
}

Output yang Dihasilkan:

text
join: Senin, Rabu, Jumat
concat: rustdanteks
--------------------| batas |--------------------

Bedah kode di atas:

  1. Baris 2 (let days = ["Senin", "Rabu", "Jumat"];): array tiga teks — struktur data yang sudah kita kenal di Section Dasar Sintaks.
  2. Baris 3 (let joined = days.join(", ");): join menyisipkan ", " di antara tiap elemen dan mengembalikan String baru Senin, Rabu, Jumat — pemisah tidak tertempel di awal maupun akhir.
  3. Baris 5 (println!("concat: {}", parts.concat());): concat menempel elemen apa adanya tanpa pemisah, menghasilkan rustdanteks — untuk teks yang dibaca manusia, join hampir selalu pilihan yang tepat.
  4. Baris 6 (let line = "-".repeat(20);): repeat menggandakan "-" dua puluh kali menjadi String; hasilnya dipakai dua kali untuk menggambar garis batas.

Ringkasan #

Slice rentang byte memotong teks sebagai pinjaman tanpa penyalinan, split dan split_whitespace memecahnya menurut pemisah, sementara join, concat, dan repeat merakit potongan menjadi String baru. Aturan keselamatan menaungi semuanya: ujung rentang harus jatuh di char boundary, dan pelanggarannya dihukum panic saat program berjalan — lengkap dengan pesan yang menunjuk huruf mana yang tercacah. Untuk teks yang tidak terjamin batasnya, get dengan pola match mengubah kemungkinan panic menjadi cabang None yang bisa ditangani. Perkakas terakhir yang belum tersentuh adalah merangkai nilai non-teks — angka, tipe lain — menjadi kalimat, dan itu wilayah format! di Chapter berikutnya.