Referensi memungkinkan banyak pihak memakai satu data sekaligus, dan di titik itulah bahaya klasik pemrograman muncul: bagaimana jika satu peminjam sedang mengubah data sementara peminjam lain sedang membacanya? Pembaca bisa mendapat data setengah jadi, penunjuk bisa menunjuk ke wilayah yang sudah dipindahkan, dan di program paralel masalah ini bernama data race. Rust menjawab dengan satu aturan borrowing yang ringkas namun mengikat: pada satu waktu, sebuah nilai hanya boleh dipinjam sebagai satu referensi mutable atau sejumlah referensi immutable — tidak pernah keduanya bersamaan. Pada Chapter ini kita akan menguji aturan tersebut dari tiga sisi: banyak pembaca, dua penulis, dan campuran keduanya.
Bunyi Aturan dan Alasannya #
Secara formal, aturan borrowing berbunyi: di setiap titik waktu, sebuah nilai boleh memiliki tepat satu referensi mutable (&mut T) atau sejumlah referensi immutable (&T) yang aktif, tetapi tidak boleh keduanya sekaligus. Aturan ini paling sering diringkas sebagai one writer XOR many readers.
Dua sisi aturan tersebut melindungi dua jaminan berbeda. Larangan dua penulis sekaligus mencegah dua proses saling menimpa isi data secara tak terduga. Larangan penulis bersama pembaca mencegah pembaca menerima data setengah ter-update. Sebagai bonus, aturan yang sama otomatis melindungi program paralel dari data race — topik yang akan kita buka lebar di section Concurrency nanti. Kunci pentingnya: pemeriksaan dilakukan kompilator pada saat kompilasi, tanpa biaya runtime apa pun.
Banyak Referensi Immutable Diperbolehkan #
Sisi pertama aturan longgar: pembaca tidak saling mengganggu. Sekumpulan referensi immutable boleh aktif bersamaan karena tidak ada satu pun yang bisa mengubah data.
fn main() {
let name = String::from("Siti");
let r1 = &name;
let r2 = &name;
let r3 = &name;
println!("r1: {}, r2: {}, r3: {}", r1, r2, r3);
}Output yang Dihasilkan:
r1: Siti, r2: Siti, r3: SitiMari kita bedah kode di atas:
- Baris 2 (
let name = ...):nameadalah pemilik tunggal teks"Siti", sesuai aturan ownership. - Baris 4–6 (
let r1 = &name;dst.): tiga reference immutable dibuat secara berurutan. Ketiganya aktif pada saat yang sama — dan sah — karena semuanya hanya membaca. - Baris 8 (
println! ...): ketiga peminjam dipakai bersamaan dalam satu pemanggilan. Selamanametidak diubah, sepuluh pembaca pun tidak akan menimbulkan masalah.
Pola ini adalah kenyataan sehari-hari kode Rust: operasi baca dapat berlangsung massal dan paralel tanpa koordinasi apa pun.
Kode yang Gagal: Dua Referensi Mutable #
Sisi ketat aturan diuji ketika kita membuat penulis kedua. Kode berikut sengaja meminjam balance sebagai mutable dua kali — dan memang tidak dapat dikompilasi:
fn main() {
let mut balance = String::from("1000");
let loan_a = &mut balance;
let loan_b = &mut balance;
loan_a.push_str("000");
println!("Saldo akhir: {}", loan_b);
}Pesan error kompilator:
error[E0499]: cannot borrow `balance` as mutable more than once at a time
--> main.rs:5:18
|
4 | let loan_a = &mut balance;
| ------------ first mutable borrow occurs here
5 | let loan_b = &mut balance;
| ^^^^^^^^^^^^ second mutable borrow occurs here
6 |
7 | loan_a.push_str("000");
| ------ first borrow later used here
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0499`.Pesan E0499 menunjuk tiga peristiwa secara berurutan: pinjaman mutable pertama pada baris 4, pinjaman mutable kedua pada baris 5, dan pemakaian pinjaman pertama pada baris 7. Frasa first borrow later used here adalah kuncinya — karena loan_a masih dipakai setelah loan_b lahir, kedua pinjaman dianggap hidup bersamaan, dan aturan satu penulis dilanggar. Bayangkan jika kedua pinjaman ini diizinkan: loan_a menambahkan "000" sementara loan_b juga berhak menulis — saldo akhir menjadi tidak terprediksi.
Kode yang Gagal: Campuran Pembaca dan Penulis #
Pelanggaran kedua lebih halus: satu pembaca dan satu penulis. Kode berikut juga tidak dapat dikompilasi:
fn main() {
let mut name = String::from("Budi");
let reader = &name;
let writer = &mut name;
println!("Nama: {}", reader);
println!("Nama: {}", writer);
}Pesan error kompilator:
error[E0502]: cannot borrow `name` as mutable because it is also borrowed as immutable
--> main.rs:5:18
|
4 | let reader = &name;
| ----- immutable borrow occurs here
5 | let writer = &mut name;
| ^^^^^^^^^ mutable borrow occurs here
6 |
7 | println!("Nama: {}", reader);
| ------ immutable borrow later used here
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0502`.Pesan E0502 menjelaskan kronologi yang sama dengan sudut pandang campuran: reader pinjaman immutable lahir di baris 4, writer pinjaman mutable lahir di baris 5, dan reader masih dipakai di baris 7 — dua pinjaman berbeda jenis hidup bersamaan. Penolakan ini terasa ketat, tetapi alasannya nyata: reader berpotensi membaca nama saat writer sedang mengubahnya, dan hasil pembacaan bisa berupa teks yang setengah lama setengah baru.
Kapan Sebuah Pinjaman Dianggap Selesai? #
Pembaca yang teliti akan menemukan celah penting pada dua pesan error di atas: keduanya sama-sama menekankan pemakaian setelahnya (later used). Pinjaman tidak dihitung sejak kurung kurawal penutup scope, melainkan sejak pemakaian terakhirnya — prinsip ini dikenal sebagai Non-Lexical Lifetimes (NLL). Referensi yang sudah tidak dipakai lagi dianggap selesai meminjam, meskipun variabelnya secara leksikal masih ada.
Konsekuensinya, kode berikut sepenuhnya sah:
fn main() {
let mut city = String::from("Bandung");
let reader = &city;
println!("Kota saat ini: {}", reader);
let writer = &mut city;
writer.push_str(" Kota");
println!("Kota setelah diubah: {}", writer);
}Output yang Dihasilkan:
Kota saat ini: Bandung
Kota setelah diubah: Bandung KotaMari kita bedah mengapa kali ini diterima:
- Baris 4 (
let reader = &city;):readermeminjamcitysecara immutable. - Baris 5 (
println!("Kota saat ini: {}", reader);): pemakaian terakhirreader. Sejak baris ini, pinjamanreaderdianggap selesai — meskipun variabelreadermasih berdiri di kode. - Baris 7 (
let writer = &mut city;): karena tidak ada lagi pembaca aktif, pinjaman mutable baru sah dibuat. Inilah titik yang membuat contoh ini berbeda dari kasus E0502 sebelumnya. - Baris 8 (
writer.push_str(" Kota");): satu-satunya penulis aktif mengubah isi teks menjadi"Bandung Kota"tanpa pesaing. - Baris 9 (
println! ...): pembacaan terakhir lewatwritersekaligus penutup pinjaman mutable tersebut.
Ringkasan #
Aturan borrowing menyatakan satu penulis atau banyak pembaca, tidak pernah campuran, dan pemeriksaannya berlangsung saat kompilasi. Kita telah melihat tiga wujudnya: tiga pembaca yang hidup rukun (E0499 tidak berlaku), dua penulis yang ditolak E0499, serta campuran pembaca-penulis yang ditolak E0502. Prinsip NLL menambahkan nuansa penting: pinjaman berakhir pada pemakaian terakhirnya, sehingga urutan pemakaian — bukan sekadar urutan deklarasi — menentukan sah tidaknya kode. Seluruh permainan penulisan dan pembacaan ini berpangkal pada satu alat yang belum kita pegang penuh: referensi mutable itu sendiri.