Lewati ke konten

Borrowing di Rust

Di halaman ownership kamu melihat sebuah masalah: mengirim String ke fungsi akan memindahkannya (move), jadi kamu tidak bisa memakainya lagi sesudahnya. Kalau setiap nilai harus terus dipindah ke sana kemari (atau di-clone), menulis kode akan merepotkan. Borrowing (peminjaman) adalah solusi dari Rust.

Borrowing artinya membiarkan sebagian kode memakai sebuah nilai tanpa mengambil ownership-nya, seperti meminjamkan buku ke teman. Dia boleh membacanya, lalu mengembalikannya, dan buku itu tetap milikmu.

Kamu membuat referensi dengan menaruh & di depan sebuah nilai. Referensi membuat sebuah fungsi bisa melihat data tanpa memilikinya:

fn print_length(s: &String) { // meminjam s, tidak memilikinya
println!("Panjang: {}", s.len());
}
fn main() {
let text = String::from("halo");
print_length(&text); // pinjamkan text ke fungsi
println!("Masih milikku: {text}"); // ✅ text masih valid di sini
}

Output:

Panjang: 4
Masih milikku: halo
▶ Coba sendiri

&text artinya “biarkan fungsi ini meminjam text.” Karena fungsinya hanya meminjam, ownership tetap ada di main, dan kamu masih bisa memakai text sesudahnya. Bandingkan dengan halaman ownership, di mana kode yang sama tanpa & menghasilkan error.

Referensi mutable: meminjam untuk mengubah

Section titled “Referensi mutable: meminjam untuk mengubah”

Referensi & biasa bersifat read-only (hanya bisa dibaca): kamu boleh melihat, tapi tidak boleh menyentuh. Kalau sebuah fungsi perlu mengubah nilai yang dipinjamnya, pakai referensi mutable dengan &mut:

fn add_excitement(s: &mut String) {
s.push_str("!!!");
}
fn main() {
let mut text = String::from("halo");
add_excitement(&mut text); // pinjamkan sebagai mutable
println!("{text}"); // halo!!!
}

Output:

halo!!!
▶ Coba sendiri

Perhatikan, ada tiga hal yang harus cocok: text dideklarasikan dengan mut, fungsinya meminta &mut String, dan pemanggilannya mengirim &mut text. Ketiganya wajib ada untuk mengubah nilai yang dipinjam.

Rust menegakkan dua aturan tentang referensi. Aturan ini ada untuk mencegah data race, yaitu sekelompok bug di mana dua bagian kode menyentuh data yang sama pada saat yang sama lalu merusaknya.

Pada satu waktu, kamu boleh punya salah satu dari ini:

  • berapa pun referensi read-only (&), atau
  • tepat satu referensi mutable (&mut),

tapi tidak keduanya sekaligus.

Dalam bahasa sehari-hari: banyak pembaca atau satu penulis, tidak pernah keduanya bersamaan. Kode ini melanggar aturan itu:

fn main() {
let mut text = String::from("halo");
let r1 = &text; // borrow read-only
let r2 = &mut text; // ❌ tidak bisa borrow sebagai mutable selama r1 masih ada
println!("{r1} {r2}");
}

Compiler menghentikanmu:

error[E0502]: cannot borrow `text` as mutable because it is also borrowed as immutable

Artinya: text tidak bisa dipinjam sebagai mutable karena sedang dipinjam sebagai immutable.

Mungkin kamu mengira sebuah borrow bertahan sampai } penutup scope-nya. Ternyata tidak. Sebuah borrow hanya bertahan sampai tempat terakhir referensi itu dipakai. Setelah itu, nilainya bebas dipinjam lagi, bahkan sebagai mutable.

Artinya, contoh yang rusak di atas bisa diperbaiki cukup dengan selesai memakai r1 sebelum kamu membuat r2:

fn main() {
let mut text = String::from("halo");
let r1 = &text;
println!("Membaca: {r1}"); // pemakaian terakhir r1, jadi borrow-nya berakhir di sini
let r2 = &mut text; // ✅ aman: tidak ada yang sedang membaca lagi
r2.push_str(", dunia");
println!("{text}");
}

Output:

Membaca: halo
halo, dunia
▶ Coba sendiri

Aturannya tetap “banyak pembaca atau satu penulis”. Hanya saja, aturan itu diperiksa sepanjang bagian kode tempat setiap referensi benar-benar dipakai. Pembaca r1 sudah selesai saat penulis r2 muncul, dan r2 sudah selesai sebelum println! terakhir membaca text lagi. Tidak ada yang tumpang tindih, jadi Rust senang.

Inilah cara paling umum untuk memperbaiki error borrow: ubah urutannya. Selesaikan semua pembacaan yang kamu perlukan, lalu penulisannya (atau sebaliknya), alih-alih mencampur keduanya.

Semua aturan ini diperiksa sepenuhnya saat kompilasi, jadi tidak ada biaya sama sekali saat program berjalan. Begitulah Rust bisa menjanjikan “tidak ada data race” sambil tetap secepat C: keamanannya sudah dibuktikan sebelum program mulai berjalan.

Coba kerjakan sendiri dulu, di Rust Playground atau di proyek lokal. Kalau compiler protes, baca error-nya: biasanya ia menunjukkan dengan tepat di mana borrow-nya tumpang tindih.

Tulis fungsi total_letters yang meminjam sebuah Vec<String> dan mengembalikan jumlah huruf dari semua katanya. Setelah memanggilnya, main harus tetap bisa mencetak vektor itu.

Petunjuk: tipe parameternya &Vec<String>, dan kamu memanggilnya dengan &words. Lakukan perulangan pada vektor yang dipinjam dengan for word in list, lalu jumlahkan word.len().

Lihat solusi
fn total_letters(list: &Vec<String>) -> usize {
let mut total = 0;
for word in list {
total += word.len();
}
total
}
fn main() {
let words = vec![String::from("borrow"), String::from("check")];
let count = total_letters(&words); // pinjamkan vektornya
println!("{count} huruf");
println!("{:?} masih milik main", words);
}

Output:

11 huruf
["borrow", "check"] masih milik main
▶ Coba sendiri

Fungsi ini hanya perlu melihat kata-katanya, jadi borrow & yang read-only sudah cukup. Kalau kamu menulis list: Vec<String>, vektornya akan pindah ke dalam fungsi dan println! terakhir tidak akan bisa dikompilasi.

Tulis dua fungsi: add_item, yang menambahkan barang ke daftar belanja, dan print_list, yang mencetak setiap barang. Tambahkan tiga barang, lalu cetak daftarnya.

Petunjuk: satu fungsi mengubah daftarnya dan satu lagi hanya membacanya. Mana yang butuh &mut Vec<String>, dan mana yang cukup dengan &Vec<String>? Untuk mengubah &str menjadi String, pakai String::from(item).

Lihat solusi
fn add_item(list: &mut Vec<String>, item: &str) {
list.push(String::from(item));
}
fn print_list(list: &Vec<String>) {
for item in list {
println!("- {item}");
}
}
fn main() {
let mut list = Vec::new();
add_item(&mut list, "susu");
add_item(&mut list, "roti");
add_item(&mut list, "apel");
print_list(&list);
}

Output:

- susu
- roti
- apel
▶ Coba sendiri

add_item mengubah daftarnya, jadi ia mendapat borrow &mut, dan list di main harus mut. print_list hanya membaca, jadi & biasa sudah cukup. Setiap borrow &mut list berakhir saat pemanggilannya selesai, itulah sebabnya pemanggilan berikutnya bisa meminjamnya lagi.

Program ini tidak bisa dikompilasi. Perbaiki supaya ia mencetak Skor: 10 poin, dengan tetap mempertahankan first dan second.

fn main() {
let mut message = String::from("Skor:");
let first = &mut message;
let second = &mut message;
first.push_str(" 10");
second.push_str(" poin");
println!("{message}");
}
error[E0499]: cannot borrow `message` as mutable more than once at a time
--> src/main.rs:4:18
|
3 | let first = &mut message;
| ------------ first mutable borrow occurs here
4 | let second = &mut message;
| ^^^^^^^^^^^^ second mutable borrow occurs here
5 | first.push_str(" 10");
| ----- first borrow later used here

Compiler bilang message tidak bisa dipinjam sebagai mutable lebih dari sekali pada saat yang sama.

Petunjuk: hanya satu penulis pada satu waktu. Lihat lagi bagian Kapan sebuah borrow berakhir? di atas: bisakah kamu selesai memakai first sebelum second dibuat?

Lihat solusi
fn main() {
let mut message = String::from("Skor:");
let first = &mut message;
first.push_str(" 10"); // pemakaian terakhir first: borrow-nya berakhir di sini
let second = &mut message;
second.push_str(" poin");
println!("{message}");
}

Output:

Skor: 10 poin
▶ Coba sendiri

Di kode aslinya, first masih akan dipakai (di baris 5) saat second dibuat di baris 4. Itu berarti ada dua penulis pada saat yang sama. Dengan memindahkan first.push_str ke atas, first sudah selesai sebelum second ada, jadi selalu hanya ada satu &mut pada satu waktu.

  • Borrowing (&) membuat kode bisa memakai sebuah nilai tanpa mengambil ownership-nya: nilainya kembali kepadamu.
  • Referensi & biasa bersifat read-only; pakai &mut untuk mengubah nilai yang dipinjam.
  • Untuk mengubah nilai lewat referensi, variabelnya, parameternya, dan pemanggilannya semua butuh mut/&mut.
  • Aturannya: banyak pembaca atau satu penulis, tidak pernah keduanya sekaligus.
  • Sebuah borrow berakhir di pemakaian terakhir referensinya, jadi mengubah urutan kode sering kali memperbaiki error borrow.
  • Semua ini diperiksa saat kompilasi, jadi gratis saat program berjalan.

Kuis singkat

1. Apa efek menaruh & di depan sebuah nilai saat mengirimnya ke fungsi?

2. Menurut aturan borrowing, kombinasi mana yang boleh ada pada saat yang sama?

3. Apa yang kamu butuhkan untuk mengubah sebuah nilai lewat referensi?

4. Kenapa Rust bisa menegakkan aturan borrowing tanpa biaya performa saat runtime?

Skor: 0 / 4