Teks adalah data yang paling sering kita potong: ambil kata pertama dari kalimat, potong ekstensi dari nama file, atau ambil nama pengguna dari alamat surel. Kita sudah memiliki String sebagai pemilik teks di heap, dan sekarang kita butuh alat untuk meminjam sebagian isinya. Alat itu adalah string slice bertipe &str — saudara kandung dari slice &[i32] yang baru saja kita pelajari, dengan karakter khusus: elemen yang dipinjamnya adalah byte-byte penyusun teks. Pada Chapter ini kita akan memotong teks dengan notasi rentang, mengenali string literal sebagai slice, membangun fungsi pencari kata pertama, lalu menyaksikan borrow checker menjaga potongan teks itu tetap valid.

Apa Itu String Slice #

&str adalah slice atas rangkaian byte penyusun sebuah teks. Struktur mesinnya sama dengan &[i32]: satu pointer menuju byte pertama yang dipinjam, plus panjang berupa jumlah byte yang terlihat. Perbedaannya hanya pada jenis data yang ditunjuk — byte teks, bukan angka — dan pada notasi pemotongannya: kita menulis rentang langsung pada variabel teks, seperti &kalimat[0..7].

Satu hal penting sebelum memotong: angka rentang pada &str menghitung byte, bukan karakter. Untuk teks Latin biasa seperti yang kita pakai sepanjang Course ini, satu karakter menempati satu byte, sehingga angka indeks dan posisi karakter terasa sama. Pembahasan tuntas tentang teks Unicode — di mana satu karakter bisa terdiri dari beberapa byte — menyusul di section String dan Teks.

Meminjam Potongan Teks #

Kita potong sebuah kalimat dengan beberapa variasi rentang sekaligus:

rust
fn main() {
    let sentence = String::from("Belajar Rust itu seru");

    let first = &sentence[0..7];
    let second = &sentence[8..12];
    let tail = &sentence[13..];
    let whole = &sentence[..];

    println!("first  : '{}'", first);
    println!("second : '{}'", second);
    println!("tail   : '{}'", tail);
    println!("whole  : '{}'", whole);
}

Output yang Dihasilkan:

text
first  : 'Belajar'
second : 'Rust'
tail   : 'itu seru'
whole  : 'Belajar Rust itu seru'

Mari kita bedah kode di atas baris-per-baris:

  1. Baris 2 (let sentence = String::from("Belajar Rust itu seru");): sentence menjadi pemilik teks 21 byte di heap. Hitungan byte dimulai dari nol: huruf B menempati byte 0, huruf r terakhir di kata pertama menempati byte 6, dan spasi pertama menempati byte 7.
  2. Baris 4 (let first = &sentence[0..7];): rentang 0..7 meminjam byte 0 sampai 6 — tujuh byte pertama, membentuk teks Belajar. Byte 7 yang berupa spasi tidak ikut karena batas akhir eksklusif.
  3. Baris 5 (let second = &sentence[8..12];): rentang ini meminjam byte 8 sampai 11, yaitu R, u, s, t — kata kedua. Angka-angkanya kita hitung dari posisi byte di teks, bukan dari nomor kata.
  4. Baris 6 (let tail = &sentence[13..];): batas awal 13 tanpa batas akhir berarti “dari byte 13 sampai habis”. Bentuk eksplisit &sentence[13..21] memang bisa ditulis, tetapi bentuk [13..] lebih aman dari salah hitung karena panjang teks dihitung otomatis.
  5. Baris 7 (let whole = &sentence[..];): tanpa batas kiri dan kanan, slice meminjam teks utuh dari byte 0 sampai akhir — semua byte, satu pun tidak terlewat.
  6. Baris 9–12 (println! berempat): setiap potongan dicetak. {} pada println! sudah otomatis menampilkan isi &str sebagai teks biasa, sama seperti String.

Perhatikan bahwa sentence tetap utuh — tidak ada teks yang dipindahkan atau terpotong secara permanen. Keempat variabel hanya jendela pinjaman yang berbeda-beda lebar jangkauannya.

String Literal juga String Slice #

Sejauh ini kita memotong teks milik String. Bagaimana dengan string literal seperti "Rust itu seru" yang kita tulis langsung di kode? Literal semacam itu bukan String — literal adalah teks yang dibakar ke dalam binary program, dan tipenya memang &str. Literal dengan sendirinya adalah string slice yang meminjam teks di memori program:

rust
fn main() {
    let literal = "Rust itu seru";
    let view = &literal[0..4];

    println!("Literal utuh : '{}'", literal);
    println!("Potongannya  : '{}'", view);
}

Output yang Dihasilkan:

text
Literal utuh : 'Rust itu seru'
Potongannya  : 'Rust'

Mari kita bedah kode di atas:

  1. Baris 2 (let literal = "Rust itu seru";): teks 13 byte ini tersimpan di dalam binary program, bukan di heap yang dikelola variabel. Variabel literal bertipe &str — sebuah slice yang menunjuk area binary tersebut.
  2. Baris 3 (let view = &literal[0..4];): pemotongan bekerja persis seperti pada String. Bedanya sumber pinjaman: view meminjam byte-byte di binary program, bukan byte-byte di heap milik sebuah String.

Fungsi Pencari Kata Pertama #

Gabungan pemotongan teks dan referensi menghasilkan pola fungsi yang sangat lazim: fungsi yang menerima teks pinjaman lalu mengembalikan potongan dari teks itu. Kita bangun first_word, pencari kata pertama:

rust
fn first_word(text: &str) -> &str {
    let bytes = text.as_bytes();
    let mut end = 0;

    while end < bytes.len() {
        if bytes[end] == b' ' {
            return &text[..end];
        }
        end += 1;
    }

    &text[..]
}

fn main() {
    let sentence = String::from("Belajar Rust itu seru");
    let word = first_word(&sentence);

    println!("Kata pertama: '{}'", word);
}

Output yang Dihasilkan:

text
Kata pertama: 'Belajar'

Mari kita bedah fungsi tersebut:

  1. Baris 1 (fn first_word(text: &str) -> &str): parameter dan nilai kembalian sama-sama &str. Fungsi tidak memiliki teks; fungsi hanya menerima pinjaman dan mengembalikan potongan pinjaman dari teks yang sama. Kompilator berhasil memahami hubungan pinjam-meminjam ini tanpa anotasi tambahan — aturan lengkap untuk mendeskripsikannya secara eksplisit menyusul di section Lifetime.
  2. Baris 2 (let bytes = text.as_bytes();): as_bytes() memberikan kita tampilan &[u8] — rangkaian byte penyusun teks, siap dibaca satu per satu.
  3. Baris 5–10 (while end < bytes.len() beserta isinya): perulangan berjalan dari byte 0. Notasi b' ' adalah byte literal untuk spasi. Begitu spasi ditemukan di posisi end, baris 7 mengembalikan &text[..end] — potongan teks dari awal sampai sebelum spasi.
  4. Baris 12 (&text[..]): jika perulangan selesai tanpa menemukan spasi, teks dianggap satu kata utuh dan dikembalikan selengkapnya — satu kata tanpa spasi memang kata pertamanya sekaligus terakhirnya.
  5. Baris 17 (let word = first_word(&sentence);): &sentence berupa &String, dan Rust menyesuaikannya menjadi &str secara otomatis di titik pemanggilan — mekanisme penyetaraan yang akan kita bahas lebih dalam pada Chapter Slice sebagai Parameter.

Memutus Pinjaman #

Nilai kembalian first_word adalah pinjaman, dan pinjaman tunduk pada aturan borrowing. Kode berikut mencoba mengosongkan teks sementara potongan kata pertamanya masih dipakai — dan ditolak keras oleh kompilator:

rust
fn first_word(text: &str) -> &str {
    let bytes = text.as_bytes();
    let mut end = 0;

    while end < bytes.len() {
        if bytes[end] == b' ' {
            return &text[..end];
        }
        end += 1;
    }

    &text[..]
}

fn main() {
    let mut sentence = String::from("Belajar Rust itu seru");
    let word = first_word(&sentence);

    sentence.clear();

    println!("Kata pertama: '{}'", word);
}

Pesan error kompilator:

text
error[E0502]: cannot borrow `sentence` as mutable because it is also borrowed as immutable
  --> main.rs:19:5
   |
17 |     let word = first_word(&sentence);
   |                           --------- immutable borrow occurs here
18 |
19 |     sentence.clear();
   |     ^^^^^^^^^^^^^^^^ mutable borrow occurs here
20 |
21 |     println!("Kata pertama: '{}'", word);
   |                                    ---- immutable borrow later used here

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0502`.

Kronologi pada pesan E0502 mudah dibaca: baris 17 mencatat pinjaman immutable lahir saat first_word(&sentence) dipanggil, baris 19 mencatat upaya pinjaman mutable oleh sentence.clear(), dan baris 21 mencatat pinjaman immutable yang pertama masih dipakai lewat word. Aturan satu penulis atau banyak pembaca dari Chapter Aturan Borrowing terlanggar, dan pesan error mencatat pelanggarannya secara berurutan. Penyebab teknisnya juga serius: clear() membongkar isi heap milik sentence, sehingga word yang menunjuk ke sana akan menggantung. Pembenahannya tinggal soal urutan — selesaikan dulu pencetakan word, baru panggil sentence.clear() setelah pinjaman selesai.

Ringkasan #

&str adalah string slice: pointer plus panjang yang meminjam sebagian atau seluruh byte teks. Notasi rentang [0..7], [13..], dan [..] membuka jendela pinjaman di atas String maupun string literal — dan literal itu sendiri sudah bertipe &str. Fungsi seperti first_word menegaskan pola menerima-mengembalikan pinjaman teks, sementara E0502 menunjukkan bahwa potongan teks tetap dijaga aturan borrowing hingga pinjaman selesai. Berikutnya kita membawa slice ke dua wadah data lain — array dan Vec — dengan seluruh keluarga notasi rentangnya.