Error Rust E0499: cannot borrow as mutable more than once at a time
Error E0499 artinya kamu mencoba punya dua referensi mutable (&mut) ke nilai yang sama pada saat bersamaan. Ini salah satu error borrow checker (pemeriksa peminjaman) yang paling umum, dan perbaikannya biasanya kecil: sering kali kamu cukup memindahkan satu baris. (Mencari error lain? Lihat penjelasan error compiler.)
Pesan error-nya
Section titled “Pesan error-nya”error[E0499]: cannot borrow `balance` as mutable more than once at a time --> src/main.rs:5:20 |4 | let deposit = &mut balance; | ------------ first mutable borrow occurs here5 | let withdraw = &mut balance; | ^^^^^^^^^^^^ second mutable borrow occurs here6 |7 | *deposit += 50; | -------------- first borrow later used hereDalam bahasa sederhana: “balance tidak bisa dipinjam sebagai mutable lebih dari sekali pada saat yang sama”.
Ini program yang menghasilkan error tersebut:
fn main() { let mut balance = 100;
let deposit = &mut balance; let withdraw = &mut balance;
*deposit += 50; *withdraw -= 30;
println!("Balance: {balance}");}Apa artinya
Section titled “Apa artinya”Compiler menunjuk tiga tempat, dan bersama-sama ketiganya menceritakan semuanya:
- Baris 4:
depositmenjadi mutable borrow atasbalance. - Baris 5:
withdrawmencoba menjadi mutable borrow kedua atasbalance. - Baris 7:
depositmasih dipakai setelah baris 5. Jadi borrow pertama masih hidup saat borrow kedua dimulai.
Label ketiga itulah yang penting. Dua borrow &mut hanya jadi masalah kalau masa hidupnya tumpang tindih. Kalau deposit sudah selesai dipakai sebelum baris 5, tidak akan ada error.
Bayangkan sebuah dokumen bersama dengan satu “kunci edit”. Siapa pun yang memegang kunci itu boleh mengubah dokumennya. Rust tidak mau menggandakan kuncinya: orang kedua harus menunggu sampai orang pertama mengembalikannya.
Kenapa Rust melakukan ini
Section titled “Kenapa Rust melakukan ini”Aturan borrowing (peminjaman) di Rust: pada satu saat, kamu boleh punya banyak pembaca (&) atau satu penulis (&mut), tidak pernah lebih dari satu penulis. Referensi &mut adalah sebuah janji: “selama aku ada, tidak ada orang lain yang boleh menyentuh nilai ini.”
Janji itu mencegah bug sungguhan. Kalau dua bagian kode bisa mengubah nilai yang sama sekaligus, yang satu bisa mengubah ukuran sebuah Vec sementara yang lain masih menunjuk ke memori lamanya. Di bahasa lain, itu berarti crash atau data rusak tanpa ketahuan. Antar-thread, itu namanya data race.
Berkat aturan ini, compiler bisa yakin bahwa sebuah &mut adalah satu-satunya jalan untuk mencapai datanya selama ia hidup. Kamu mendapat keamanan itu tanpa biaya runtime sama sekali. Harganya: kadang kamu harus menunjukkan ke compiler dengan lebih jelas bahwa dua borrow-mu sebenarnya tidak bertabrakan.
Cara memperbaikinya
Section titled “Cara memperbaikinya”Perbaikan 1: Selesaikan borrow pertama sebelum memulai yang kedua
Section titled “Perbaikan 1: Selesaikan borrow pertama sebelum memulai yang kedua”Coba ini dulu. Cara ini memperbaiki sebagian besar error E0499. Sebuah borrow bertahan sampai pemakaian terakhirnya, bukan sampai akhir blok. Jadi kerjakan semuanya dengan referensi pertama, baru buat yang kedua:
fn main() { let mut balance = 100;
let deposit = &mut balance; *deposit += 50; // pemakaian terakhir `deposit`: borrow-nya berakhir di sini
let withdraw = &mut balance; // ✅ sekarang tidak ada yang meminjam *withdraw -= 30;
println!("Saldo: {balance}");}Output:
Saldo: 120Sering kali kamu bahkan tidak butuh variabel tambahan itu: balance += 50; lalu balance -= 30; juga bekerja dengan baik.
Perbaikan 2: Pinjam bagian-bagian yang tidak tumpang tindih
Section titled “Perbaikan 2: Pinjam bagian-bagian yang tidak tumpang tindih”Kadang kamu memang butuh dua &mut sekaligus, tapi keduanya menunjuk ke bagian yang berbeda dari data. Compiler tidak selalu bisa melihatnya, jadi kamu yang menunjukkannya.
Untuk dua elemen sebuah Vec atau slice, tasks[0] dan tasks[1] sama-sama meminjam seluruh tasks. Pakai split_at_mut (bahasa Inggris) untuk memotongnya menjadi dua bagian yang dijamin tidak tumpang tindih:
fn main() { let mut tasks = vec![String::from("write"), String::from("test")];
// Belah Vec menjadi dua bagian yang tidak tumpang tindih let (left, right) = tasks.split_at_mut(1); let first = &mut left[0]; let second = &mut right[0];
std::mem::swap(first, second); println!("{tasks:?}");}Output:
["test", "write"](Khusus untuk menukar, tasks.swap(0, 1) bahkan lebih sederhana. split_at_mut untuk saat kamu butuh kedua referensi itu untuk pekerjaan sungguhan.)
Untuk sebuah struct, error yang sama muncul saat kamu memanggil dua method yang masing-masing menerima &mut self, seperti p.name_mut() dan p.score_mut(). Setiap pemanggilan meminjam seluruh struct. Kalau kamu meminjam field-nya secara langsung, compiler bisa melihat bahwa keduanya terpisah:
struct Player { name: String, score: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), score: 0 };
// Pinjam field-nya langsung, bukan lewat method let name = &mut p.name; let score = &mut p.score;
name.push_str(" si Kepiting"); *score += 10; println!("{} punya {} poin", p.name, p.score);}Output:
Ferris si Kepiting punya 10 poinPerbaikan 3: Pakai indeks, jangan menyimpan referensi
Section titled “Perbaikan 3: Pakai indeks, jangan menyimpan referensi”Kasus klasik: kamu melakukan loop atas sebuah Vec dengan iter_mut() dan sekaligus mencoba push ke dalamnya di dalam loop. Iterator memegang sebuah &mut selama seluruh loop, jadi push adalah &mut yang kedua:
fn main() { let mut queue = vec![1, 2, 3]; for job in queue.iter_mut() { *job *= 10; queue.push(*job); // ❌ E0499: `queue` sudah dipinjam oleh iter_mut() }}Kalau loop-nya memang harus mengubah koleksinya sendiri, lakukan loop atas posisi. Indeks hanyalah angka, jadi ia tidak meminjam apa pun:
fn main() { let mut queue = vec![1, 2, 3];
// Loop atas posisi, bukan dengan memegang referensi ke dalam `queue` for i in 0..queue.len() { queue[i] *= 10; let job = queue[i]; queue.push(job); }
println!("{queue:?}");}Output:
[10, 20, 30, 10, 20, 30]0..queue.len() dihitung sekali, sebelum loop dimulai, jadi item-item baru tidak dikunjungi lagi. Setiap borrow queue[i] hanya bertahan satu baris.
Perbaikan 4: RefCell (jalan terakhir)
Section titled “Perbaikan 4: RefCell (jalan terakhir)”RefCell memindahkan pemeriksaan borrow dari waktu kompilasi ke waktu program berjalan (runtime). Kamu bisa memanggil borrow_mut() tanpa variabel mut, asalkan tidak ada dua mutable borrow yang hidup pada saat yang sama:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100); // perhatikan: tidak perlu `mut`
*balance.borrow_mut() += 50; // pinjam, ubah, borrow berakhir di `;` *balance.borrow_mut() -= 30;
println!("Saldo: {}", balance.borrow());}Output:
Saldo: 120Jujur saja: RefCell tidak menghapus aturannya, ia hanya memeriksanya belakangan. Kalau kamu membiarkan dua guard borrow_mut() hidup sekaligus, programnya lolos kompilasi tapi panic saat dijalankan:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100);
let mut deposit = balance.borrow_mut(); let mut withdraw = balance.borrow_mut(); // 💥 `deposit` masih hidup
*deposit += 50; *withdraw -= 30;}thread 'main' (12345) panicked at src/main.rs:7:32:RefCell already borrowedPesan panic itu artinya “RefCell sudah sedang dipinjam”. Jadi pakai RefCell hanya kalau datanya memang dibagi-pakai (misalnya di dalam Rc<RefCell<T>>) dan Perbaikan 1–3 tidak cocok. Ini bukan cara untuk membungkam compiler.
Di mana biasanya kamu melihatnya
Section titled “Di mana biasanya kamu melihatnya”- Menyimpan
&mutdi dalam loop.handles.push(&mut counter)di dalam loopforgagal dengan pesan “mutably borrowed here in the previous iteration of the loop” (sudah dipinjam mutable di putaran sebelumnya). Setiap putaran loop menambah satu&mutlagi ke nilai yang sama. Perbaikannya: simpan indeks atau nilai yang dimiliki (owned), atau ubah nilainya langsung di dalam loop. - Dua pemanggilan
get_mutpadaHashMap.let a = stock.get_mut("apples")lalulet p = stock.get_mut("pears")meminjamstockdua kali. Perbaikannya: selesaikan urusan denganasebelum memanggilget_mutlagi, atau pakaistock.get_disjoint_mut(["apples", "pears"])untuk mendapat keduanya sekaligus. - Dua closure yang mengubah variabel yang sama.
let mut inc = || count += 1;danlet mut inc2 = || count += 2;masing-masing memegang&mut countselama closure itu hidup. Perbaikannya: pakai satu closure saja, atau panggil closure pertama untuk terakhir kalinya sebelum membuat yang kedua. Lihat Closure.
Error terkait
Section titled “Error terkait”- E0502: cannot borrow as mutable because it is also borrowed as immutable: idenya sama, tapi salah satu borrow-nya adalah
&yang baca-saja. - E0382: borrow of moved value: kamu memakai sebuah nilai setelah nilai itu dipindahkan (move) ke tempat lain.
- E0106: missing lifetime specifier: Rust perlu tahu berapa lama referensi yang dikembalikan tetap hidup.
- Semua error compiler: daftar lengkap halaman error.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Kapan Rust melaporkan E0499?
2. Kamu membuat let a = &mut v;, memakai a untuk terakhir kalinya, lalu membuat let b = &mut v;. Apa yang terjadi?
3. Alat mana yang membuatmu bisa mendapat dua referensi &mut ke elemen berbeda dari slice yang sama?
Skor: 0 / 3
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- E0499 — Rust error codes index (bahasa Inggris): penjelasan resmi error ini, dengan contoh minimal.
- References and Borrowing — The Rust Book (bahasa Inggris): bab yang memperkenalkan aturan “hanya satu
&mutpada satu waktu”. - Splitting Borrows — The Rustonomicon (bahasa Inggris): kenapa field struct bisa dipinjam terpisah tapi elemen slice tidak, dan bagaimana
split_at_mutbekerja. - RefCell and Interior Mutability — The Rust Book (bahasa Inggris): kapan memindahkan pemeriksaan borrow ke runtime adalah pilihan yang tepat.