Section Ownership dan Memory memberi kita kemampuan meminjam data lewat referensi, tetapi referensi yang kita kenal sejauh ini selalu menunjuk satu nilai utuh: satu String, satu variabel, satu elemen array. Di praktik nyata, kebutuhan kita sering lebih granular — program skor hanya ingin tiga nilai tertinggi, pencari kata hanya butuh kata pertama dari sebuah kalimat, dan analitik hanya menyorot sepertiga data terakhir. Menyalin elemen-elemen itu ke wadah baru setiap kali jelas boros, sementara memindahkan kepemilikan seluruh koleksi ke fungsi penghitung kecil jelas berlebihan. Rust menjawab kebutuhan di tengah itu dengan slice — tampilan pinjaman (view) atas sebagian rangkaian elemen tanpa menyalin satu pun di antaranya.
Masalah yang Ingin Dipecahkan Slice #
Kita mulai dari masalah konkretnya. Bayangkan sebuah array skor lima peserta, dan fungsi laporan yang hanya berkepentingan dengan tiga nilai di tengah. Dengan bekal sejauh ini, ada dua jalan yang sama-sama canggung. Jalan pertama: menyalin tiga nilai itu ke array baru — setiap laporan menambah alokasi memori, dan dua salinan data yang sama harus dirawat seiring program tumbuh. Jalan kedua: meminjam seluruh array lewat referensi, lalu melempar-lempar pasangan angka indeks batas awal dan akhir sebagai parameter terpisah — pemanggil dan fungsi harus sama-sama menjaga angka-angka itu tetap konsisten, dan satu kekeliruan angka menghasilkan akses di luar jangkauan.
Kedua jalan itu memecahkan masalah yang seharusnya diselesaikan oleh satu nilai. Slice menyatukan “alamat mulai” dan “berapa banyak elemen” ke dalam satu tipe data bawaan, sehingga jendela tampilan atas data menjadi nilai yang bisa dipinjam, dikirim ke fungsi, dan diperiksa oleh borrow checker seperti referensi biasa.
Apa Itu Slice #
Slice adalah referensi ke rangkaian elemen yang tersusun berurutan di memori, bukan salinan elemennya. Tipe slice untuk angka ditulis &[i32] — dibaca “reference to a slice of i32”. Sebuah slice menyimpan dua informasi: pointer menuju elemen pertama yang dipinjam, dan panjang (length) berupa jumlah elemen yang terlihat dari elemen pertama itu. Elemen yang berada di luar jangkauan panjang tersebut sama sekali tidak bisa dijangkau lewat slice — seolah-olah data lain tidak ada.
Karena slice tidak pernah memiliki data yang dilihatnya, seluruh aturan borrowing dari section sebelumnya berlaku penuh: slice adalah peminjam, pemilik array tetap menjadi pemilik, dan selama slice masih dipakai, pemilik tidak boleh mengubah susunan data yang dipinjam. Hubungan ketiganya dapat digambarkan seperti ini:
Panah penuh dari scores menandakan kepemilikan atas kelima elemen. Panah putus-putus dari view hanya menandakan jendela pandang: pointer menunjuk elemen index 1, dan panjang 3 membuat elemen index 1, 2, 3 sah dipakai sementara elemen index 0 dan 4 tidak terlihat oleh view. Tidak ada satu pun angka yang tersalin saat jendela ini dibuka.
Membuat Slice Pertama #
Slice dibuat dengan notasi rentang yang menempel pada data: &koleksi[mulai..akhir]. Kita coba pada array skor:
fn main() {
let scores = [80, 90, 70, 95, 60];
let view = &scores[1..4];
println!("Panjang slice: {}", view.len());
for position in 0..view.len() {
println!("Posisi {} bernilai {}", position, view[position]);
}
}Output yang Dihasilkan:
Panjang slice: 3
Posisi 0 bernilai 90
Posisi 1 bernilai 70
Posisi 2 bernilai 95Mari kita bedah kode di atas:
- Baris 2 (
let scores = [80, 90, 70, 95, 60];): array lima elemeni32lahir di stack, danscoresmenjadi pemiliknya. Seperti biasa pada array, jumlah elemen tetap sejak kompilasi. - Baris 3 (
let view = &scores[1..4];): rentang1..4berarti elemen index 1, 2, dan 3 — elemen index 4 tidak ikut karena batas akhir rentang bersifat eksklusif. Tanda&di depan membuat hasilnya menjadi slice&[i32]yang meminjam tiga elemen itu dariscores, bukan array baru berisi salinan. - Baris 5 (
println!("Panjang slice: {}", view.len());):view.len()mengembalikan3— panjang slice sendiri, bukan panjang array pemiliknya yang berisi lima elemen. Informasi panjang ini tersimpan di dalam slice. - Baris 7 (
for position in 0..view.len()): perulanganforberjalan di atas rentang0..3— pola yang sudah kita kenal dari section Kontrol Alur, dipakai untuk menjelajahi slice lewat indeks. - Baris 8 (
println!("Posisi {} bernilai {}", position, view[position]);):view[position]membaca elemen pada posisi relatif terhadap awal slice. Posisi0padaviewmenunjuk elemenscores[1]yang bernilai90— penomoran slice dimulai dari nol di titik tempat pointer menunjuk.
Mekanisme di Balik Layar: Pointer dan Panjang #
Secara mesin, variabel view menyimpan dua machine word: alamat elemen pertama dan panjang. Pada sistem 64-bit, keduanya berarti 16 byte total di stack — berapa pun jumlah elemen yang dilihat slice, ukuran variabelnya tetap dua kata. Menyalin atau mengirim slice ke fungsi hanya menyalin dua kata itu — biaya kecil yang tetap tidak terpengaruh jumlah elemen, jauh lebih murah daripada menyalin data itu sendiri — dan elemen-elemen di memori tidak tersentuh sama sekali. Inilah sebabnya slice disebut tampilan: data tinggal di tempatnya, yang berpindah hanyalah jendela pandangnya.
Konsekuensi kepemilikan juga tidak berubah. Ketika view keluar scope, tidak ada elemen yang di-drop — yang di-drop hanya pemilik, yaitu scores. Namun selama view masih hidup dan dipakai, borrow checker menjaga scores dengan aturan lama: tidak boleh ada pinjaman mutable lain menyentuh elemen yang sama. Slice bukan mekanisme baru di samping borrowing — slice adalah borrowing yang bekerja pada potongan data.
Akses di Luar Jangkauan #
Kejujuran panjang slice dibayar dengan pemeriksaan. Indeks pada slice tidak selalu bisa dipastikan valid saat kompilasi — angkanya bisa datang dari input atau perhitungan — sehingga Rust memeriksa setiap akses indeks saat program berjalan. Kode berikut berhasil dikompilasi, lalu berhenti dengan panic:
fn main() {
let scores = [80, 90, 70];
let view = &scores[..];
println!("Nilai pada posisi 10: {}", view[10]);
}Pesan panic saat runtime:
thread 'main' (15372) panicked at main.rs:5:42:
index out of bounds: the len is 3 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtracePesan tersebut menyebut semua fakta penting: panjang slice adalah 3, indeks yang diminta adalah 10, dan lokasi kejadian tertulis pada main.rs:5:42 — baris dan kolom ekspresi view[10]. Angka dalam tanda kurung setelah kata main adalah nomor thread sistem operasi yang berbeda pada setiap eksekusi. Program keluar dengan kode status 101, penanda khusus Rust untuk panic.
Ringkasan #
Slice menyatukan pointer dan panjang menjadi satu nilai &[T] — tampilan pinjaman atas elemen-elemen berurutan di memori. Membuat slice dengan &data[mulai..akhir] tidak menyalin data, mengirim slice ke mana pun hanya memindahkan dua kata, dan aturan borrowing tetap berlaku utuh di antara slice dan pemilik datanya. Akses indeks di luar jangkauan berhenti sebagai panic dengan pesan yang menyebut panjang dan indeks yang diminta. Dengan konsep ini di tangan, kita siap menerapkannya pada data yang paling sering kita potong: teks, melalui tipe &str pada Chapter berikutnya.