Tanda tangan fungsi adalah kontrak, dan kontrak yang salah bisa membuat fungsi berguna hanya untuk satu jenis pemanggil. Fungsi bertipe parameter &String hanya bisa menerima String — pemanggil yang memegang string literal terpaksa membuat salinan String lebih dulu. Fungsi bertipe &Vec<i32> hanya bisa menerima Vec — pemanggil dengan array harus menyalin datanya ke Vec baru terlebih dahulu. Padahal dari sudut pandang isi fungsi, yang dibutuhkan hanyalah “rangkaian elemen yang bisa dibaca”. Slice adalah tipe yang tepat untuk kontrak semacam itu: &str dan &[i32] bisa menerima pinjaman dari berbagai macam wadah. Pada Chapter ini kita akan menulis ulang dua fungsi dengan parameter slice, mempelajari penyetaraan otomatis yang terjadi di titik pemanggilan, lalu membaca penolakan kompilator ketika kepemilikan dikirim ke parameter yang meminta pinjaman.

Masalahnya: Parameter yang Terlalu Spesifik #

Mari kita rasakan masalahnya lewat contoh teks. Misalkan kita menulis fungsi shout dengan parameter &String — versi paling pertama yang masuk akal setelah memahami referensi. Fungsi tersebut menerima String pinjaman dengan mulus: shout(&message) berjalan baik. Masalah muncul ketika data kita berbentuk lain: string literal "jangan lupa berlatih" bertipe &str dan tidak bisa dikirim langsung, potongan teks &message[8..12] juga bertipe &str dan sama tertolaknya. Setiap pemanggil dengan bentuk data berbeda harus membungkus datanya menjadi String baru — alokasi heap baru hanya demi memenuhi tanda tangan yang terlalu sempit.

Pola yang sama terjadi pada angka: parameter &Vec<i32> menutup pintu bagi pemanggil dengan array [i32; 5] atau dengan potongan data. Solusi untuk keduanya sekaligus adalah menaikkan level abstraksi tanda tangan menjadi slice.

Menerima &str untuk Berbagai Sumber Teks #

Kita tulis ulang shout dengan parameter &str:

rust
fn shout(text: &str) {
    let loud = text.to_uppercase();
    println!("{}!!!", loud);
}

fn main() {
    let message = String::from("belajar rust itu seru");

    shout(&message);
    shout("jangan lupa berlatih");
    shout(&message[8..12]);
}

Output yang Dihasilkan:

text
BELAJAR RUST ITU SERU!!!
JANGAN LUPA BERLATIH!!!
RUST!!!

Mari kita bedah kode di atas:

  1. Baris 1 (fn shout(text: &str)): kontrak fungsi kini berbunyi “pinjamkan aku potongan teks apa pun”. Semua sumber teks yang menyusun byte berurutan bisa memenuhi kontrak ini.
  2. Baris 2 (let loud = text.to_uppercase();): metode to_uppercase() membaca isi pinjaman lalu membangun String baru berisi teks huruf kapital. Perhatikan pembagian perannya: loud dimiliki fungsi, sementara bahan bacanya tetap dipinjam dari pemanggil.
  3. Baris 9 (shout(&message);): &message bertipe &String, tetapi pemanggilan ini sah — Rust menyetarakan &String menjadi &str secara otomatis. Penyetaraan ini dikenal dengan nama deref coercion dan terjadi di titik pemanggilan tanpa kode tambahan dari kita.
  4. Baris 10 (shout("jangan lupa berlatih");): string literal memang sudah bertipe &str, jadi dikirim apa adanya — tidak ada konversi, tidak ada alokasi.
  5. Baris 11 (shout(&message[8..12]);): potongan teks dari String juga bertipe &str. Pemanggilan ini mengirim slice yang hanya menampung empat byte — bahan baku paling kecil sepanjang program, dan tetap diterima fungsi.

Satu tanda tangan, tiga bentuk pemanggil: &String hasil penyetaraan, literal, dan potongan. Tidak ada satu pun pemanggil yang terpaksa membuat String baru.

Menerima &[i32] untuk Array dan Vector #

Untuk data angka, peran tipe serbaguna dipegang oleh &[i32]. Kita tulis fungsi penjumlah yang bekerja untuk array, Vec, dan potongan keduanya:

rust
fn total(scores: &[i32]) -> i32 {
    let mut sum = 0;

    for position in 0..scores.len() {
        sum += scores[position];
    }

    sum
}

fn main() {
    let quiz_scores = [80, 90, 70];
    let exam_scores = vec![95, 85, 60, 75];

    println!("Total quiz: {}", total(&quiz_scores));
    println!("Total ujian: {}", total(&exam_scores));
    println!("Total sebagian ujian: {}", total(&exam_scores[2..]));
}

Output yang Dihasilkan:

text
Total quiz: 240
Total ujian: 315
Total sebagian ujian: 135

Mari kita bedah kode di atas:

  1. Baris 1 (fn total(scores: &[i32]) -> i32): kontraknya “pinjamkan aku rangkaian angka i32 berapa pun banyaknya”. Jumlah elemen bukan bagian dari tipe pada slice, sehingga satu fungsi menangani data sepanjang apa pun.
  2. Baris 4–6 (for position in 0..scores.len() beserta isinya): penjumlahan menjelajahi slice lewat indeks dengan batas scores.len() — panjang diperoleh dari slice itu sendiri, bukan dari parameter terpisah. Kemasan “data plus panjang” dari Chapter Konsep Slice terbayar di sini: kontrak fungsi menjadi satu parameter utuh tanpa pasangan indeks yang bisa saling tidak sinkron.
  3. Baris 12 (let quiz_scores = [80, 90, 70];): array tiga elemen di stack — pemanggil pertama.
  4. Baris 13 (let exam_scores = vec![95, 85, 60, 75];): Vec empat elemen di heap — pemanggil kedua.
  5. Baris 15 (total(&quiz_scores)): referensi array &[i32; 3] otomatis disetarakan menjadi &[i32]. Panjang tiga elemen ikut terbawa di dalam slice.
  6. Baris 16–17 (total(&exam_scores) dan total(&exam_scores[2..])): referensi Vec juga otomatis disetarakan menjadi &[i32], dan baris terakhir mengirim potongan yang hanya berisi elemen 60 dan 75 — totalnya 135. Tiga pemanggilan, satu definisi fungsi, nol penyalinan data.

Kesalahan Umum: Mengirim Kepemilikan ke Parameter Slice #

Setelah terbiasa memakai penyetaraan otomatis, pemula sering lupa satu hal: penyetaraan hanya berlaku untuk bentuk referensi. Mengirim kepemilikan Vec kepada parameter &[i32] tidak disetarakan apa-apa — tipe yang dikirim memang berbeda jenis. Kompilator menolaknya:

rust
fn total(scores: &[i32]) -> i32 {
    let mut sum = 0;

    for position in 0..scores.len() {
        sum += scores[position];
    }

    sum
}

fn main() {
    let exam_scores = vec![95, 85, 60];

    println!("Total ujian: {}", total(exam_scores));
}

Pesan error kompilator:

text
error[E0308]: mismatched types
  --> main.rs:14:39
   |
14 |     println!("Total ujian: {}", total(exam_scores));
   |                                 ----- ^^^^^^^^^^^ expected `&[i32]`, found `Vec<{integer}>`
   |                                 |
   |                                 arguments to this function are incorrect
   |
   = note: expected reference `&[i32]`
                 found struct `Vec<{integer}>`
note: function defined here
  --> main.rs:1:4
   |
 1 | fn total(scores: &[i32]) -> i32 {
   |    ^^^^^ --------------
help: consider borrowing here
   |
14 |     println!("Total ujian: {}", total(&exam_scores));
   |                                       +

error: aborting due to 1 previous error

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

Pesan E0308 menyebut perbedaannya terang-terangan: parameter menuntut reference &[i32], pemanggil mengirim struct Vec. Tulisan Vec<{integer}> adalah cara kompilator menyebut Vec yang elemennya belum berhasil disimpulkan karena pemanggilan ini memang gagal. Saran perbaikannya kembali ke pelajaran lama: tambahkan tanda & sehingga pemanggilan menjadi total(&exam_scores). Penyetaraan &Vec ke &[i32] barulah bekerja setelah kita meminjam.

Nilai Kembalian yang Ikut Dipinjam #

Fungsi first_word dari Chapter String Slice memperlihatkan sisi lain dari kontrak slice: nilai kembalian &str yang dipinjam dari parameter. Tanda tangan fn first_word(text: &str) -> &str menyatakan bahwa hasil keluaran adalah potongan dari pinjaman yang masuk — kompilator menyimpulkan hubungan ini secara otomatis dari bentuk tanda tangannya, tanpa anotasi tambahan pada fungsi sederhana seperti ini. Ketika tanda tangan fungsi mulai rumit — misalnya mengembalikan potongan dari salah satu di antara beberapa parameter — pendeskripsian eksplisit hubungan pinjam-meminjam menjadi wajib, dan di situlah materi anotasi lifetime dibahas tuntas di section Lifetime.

Ringkasan #

Parameter slice mengubah fungsi dari kontrak sempit menjadi kontrak serbaguna: &str menerima String, literal, dan potongan teks; &[i32] menerima array, Vec, dan potongan keduanya. Penyetaraan otomatis di titik pemanggilan bekerja satu arah dari wadah ke slice, dan hanya untuk bentuk referensi — mengirim kepemilikan Vec kepada parameter &[i32] ditolak sebagai E0308 lengkap dengan saran peminjaman. Nilai kembalian pinjaman seperti pada first_word mengikuti hubungan yang disimpulkan kompilator, dengan aturan penulisan eksplisitnya menyusul pada section Lifetime. Chapter terakhir section ini merangkum semuanya menjadi satu pedoman keputusan: kapan meminjam dengan slice, kapan memindahkan kepemilikan.