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.
Referensi: meminjam dengan &
Section titled “Referensi: meminjam dengan &”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: 4Masih milikku: halo&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!!!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.
Aturan borrowing
Section titled “Aturan borrowing”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 immutableArtinya: text tidak bisa dipinjam sebagai mutable karena sedang dipinjam sebagai immutable.
Kapan sebuah borrow berakhir?
Section titled “Kapan sebuah borrow berakhir?”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: halohalo, duniaAturannya 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.
Kenapa ini penting
Section titled “Kenapa ini penting”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.
Latihan
Section titled “Latihan”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.
1. Hitung tanpa mengambil
Section titled “1. Hitung tanpa mengambil”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 mainFungsi 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.
2. Daftar belanja
Section titled “2. Daftar belanja”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- apeladd_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.
3. Dua penulis sekaligus
Section titled “3. Dua penulis sekaligus”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 here4 | let second = &mut message; | ^^^^^^^^^^^^ second mutable borrow occurs here5 | first.push_str(" 10"); | ----- first borrow later used hereCompiler 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 poinDi 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.
Ringkasan
Section titled “Ringkasan”- Borrowing (
&) membuat kode bisa memakai sebuah nilai tanpa mengambil ownership-nya: nilainya kembali kepadamu. - Referensi
&biasa bersifat read-only; pakai&mutuntuk 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
Section titled “Kuis singkat”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
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- References and Borrowing — The Rust Book (bahasa Inggris): bab resminya, termasuk dangling reference dan kenapa Rust menolak membuatnya.
- Borrowing — Rust by Example (bahasa Inggris): contoh borrow
&yang bisa dijalankan, dengan halaman lanjutan tentang mutable borrow dan aliasing. - Non-lexical lifetimes — The Edition Guide (bahasa Inggris): perubahan yang membuat borrow berakhir di pemakaian terakhirnya, bukan di kurung kurawal penutup.
- The Slice Type — The Rust Book (bahasa Inggris): meminjam hanya sebagian dari string atau array, langkah alami berikutnya setelah referensi.
reference— dokumentasi standard library (bahasa Inggris): apa itu&Tdan&mut T, dan trait apa saja yang mereka implementasikan.