Lewati ke konten

Slice di Rust

Sebuah slice (potongan) memungkinkan kamu meminjam sebagian dari array atau vektor, semacam “jendela” ke sebagian datanya, tanpa menyalin data itu dan tanpa mengambil ownership-nya. Kalau kamu sudah pernah memakai borrowing (peminjaman) untuk satu nilai, slice adalah ide yang sama, hanya saja diterapkan pada sederet elemen.

fn main() {
let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // elemen di indeks 1, 2, 3
println!("{:?}", middle);
}

Output:

[20, 30, 40]
▶ Coba sendiri

1..4 adalah jenis range yang sama dengan yang kamu lihat di perulangan for. Range ini mulai dari indeks 1 dan berhenti sebelum indeks 4, jadi kamu mendapat indeks 1, 2, dan 3.

Beberapa jalan pintas yang sering muncul:

fn main() {
let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // dari awal sampai indeks 3 -> [10, 20, 30]
println!("{:?}", &numbers[2..]); // dari indeks 2 sampai akhir -> [30, 40, 50]
println!("{:?}", &numbers[..]); // seluruhnya -> [10, 20, 30, 40, 50]
}
▶ Coba sendiri

Sebenarnya kamu sudah memakai slice dari tadi: &str adalah slice dari sebuah String (atau dari string lain)! Itulah sebabnya kedua tipe ini bisa bekerja sama dengan sangat baik:

fn main() {
let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3];
println!("{first_word}"); // "The"
}
▶ Coba sendiri

Kalimatnya sengaja dibiarkan dalam bahasa Inggris: [0..3] memotong berdasarkan posisi byte, jadi kalau teksnya diganti, hasil potongannya juga ikut berubah.

Kamu bisa saja menyelesaikan masalah ini dengan menyalin bagian yang kamu mau ke Vec atau String baru. Slice menghindari hal itu sama sekali: slice hanya menunjuk ke sebagian data yang sudah ada, sama seperti referensi (&) menunjuk ke satu nilai. Artinya tidak ada alokasi tambahan, tidak ada penyalinan, dan aturan borrowing yang sudah kamu kenal dari halaman Borrowing tetap berlaku.

Slice sangat berguna sebagai parameter fungsi, karena bisa menerima array maupun vektor:

fn sum(numbers: &[i32]) -> i32 {
let mut total = 0;
for n in numbers {
total += n;
}
total
}
fn main() {
let array = [1, 2, 3];
let vector = vec![4, 5, 6];
println!("{}", sum(&array));
println!("{}", sum(&vector));
}
▶ Coba sendiri

&[i32] menerima slice dari array maupun dari Vec. Satu fungsi, dan kedua pemanggilan sama-sama berhasil.

Sejauh ini slice kita hanya membaca data. Sebuah slice mutable, &mut [T], memungkinkan fungsi mengubah item yang diberikan kepadanya, tetapi hanya item-item itu saja:

fn double_all(numbers: &mut [i32]) {
for i in 0..numbers.len() {
numbers[i] *= 2;
}
}
fn main() {
let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // hanya indeks 1 dan 2
println!("{:?}", scores);
double_all(&mut scores); // seluruh vektor
println!("{:?}", scores);
}

Output:

[1, 4, 6, 4, 5]
[2, 8, 12, 8, 10]
▶ Coba sendiri

Pemanggilan pertama mengirim &mut scores[1..3], jadi double_all hanya melihat indeks 1 dan 2. Semua yang ada di luar range itu aman darinya. Di dalam fungsi, slice-nya mulai lagi dari indeks 0: bagi double_all, itu hanyalah daftar berisi dua angka.

Ini aman karena sebuah aturan yang akan kamu pelajari lebih dalam di halaman Borrowing: selama fungsi memegang slice &mut, tidak ada bagian lain yang boleh memakai item-item itu, jadi dua bagian kode tidak akan pernah bisa mengubahnya bersamaan. Vektornya juga harus dideklarasikan dengan mut, kalau tidak Rust tidak akan mengizinkan kamu meminjamnya secara mutable.

Buat vektor vec![3, 6, 9, 12, 15, 18]. Cetak paruh pertama dan paruh keduanya sebagai dua slice terpisah. Petunjuk: hitung let mid = numbers.len() / 2; lalu pakai ..mid dan mid...

Lihat solusi
fn main() {
let numbers = vec![3, 6, 9, 12, 15, 18];
let mid = numbers.len() / 2;
let first_half = &numbers[..mid];
let second_half = &numbers[mid..];
println!("Paruh pertama: {:?}", first_half);
println!("Paruh kedua: {:?}", second_half);
}

Output:

Paruh pertama: [3, 6, 9]
Paruh kedua: [12, 15, 18]
▶ Coba sendiri

Karena mid dihitung dari len(), kode ini tetap benar walaupun kamu menambah atau menghapus angka. Kalau panjangnya ganjil, item sisanya masuk ke paruh kedua.

Tulis fungsi largest(numbers: &[i32]) -> i32 yang mengembalikan angka terbesar. Panggil fungsi itu dengan sebuah array, dengan sebuah vektor, dan dengan slice yang hanya berisi item pertama dari vektor tersebut. Petunjuk: mulai dari numbers[0] lalu ulangi sisanya, seperti yang biasa kamu lakukan pada array.

Lihat solusi
fn largest(numbers: &[i32]) -> i32 {
let mut biggest = numbers[0];
for n in numbers {
if *n > biggest {
biggest = *n;
}
}
biggest
}
fn main() {
let array = [7, 2, 9, 4];
let vector = vec![10, 35, 21];
println!("{}", largest(&array));
println!("{}", largest(&vector));
println!("{}", largest(&vector[..1]));
}

Output:

9
35
10
▶ Coba sendiri

Saat kamu melakukan perulangan pada slice, kamu mendapat referensi ke setiap item (&i32), bukan angkanya langsung. *n artinya “nilai yang ditunjuk oleh n”, jadi *n > biggest membandingkan dua angka biasa. Versi ini akan panic kalau slice-nya kosong, karena numbers[0] tidak ada. Nanti kamu akan belajar mengembalikan Option untuk kasus seperti itu.

  • Slice (&[T]) meminjam sebagian array atau vektor tanpa menyalinnya.
  • 1..4 memilih indeks 1 sampai 3; hilangkan salah satu sisi .. untuk mulai dari awal atau berjalan sampai akhir.
  • &str sendiri adalah slice dari teks, itulah sebabnya string dan slice punya banyak perilaku yang sama.
  • Pakai &[T] sebagai tipe parameter fungsi kalau kamu ingin menerima array maupun vektor.
  • Slice mutable (&mut [T]) memungkinkan fungsi mengubah hanya item yang diberikan kepadanya.

Kuis singkat

1. Apa yang bisa kamu lakukan dengan slice terhadap sebagian array atau vektor?

2. Indeks mana saja yang termasuk dalam &numbers[1..4]?

3. Kenapa &[i32] pilihan yang baik untuk tipe parameter fungsi?

Skor: 0 / 3