Error Rust E0502: cannot borrow as mutable because it is also borrowed as immutable
E0502 artinya kamu mencoba mengubah sebuah nilai selagi bagian lain dari kodemu masih membacanya lewat sebuah referensi. Error ini sangat sering muncul saat bekerja dengan vektor, dan perbaikannya biasanya cukup memindahkan satu baris atau menyalin satu nilai. Halaman ini memberi perbaikannya dulu, lalu menjelaskan kenapa Rust bersikeras soal ini. Untuk error lain di seri ini, lihat Penjelasan error compiler Rust.
Pesan error-nya
Section titled “Pesan error-nya”error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/main.rs:4:5 |3 | let first = &v[0]; // referensi ke dalam Vec | - immutable borrow occurs here4 | v.push(4); // ❌ mengubah Vec selagi `first` masih hidup | ^^^^^^^^^ mutable borrow occurs here5 | println!("First: {first}"); // ...karena `first` dipakai di sini | ----- immutable borrow later used hereDalam bahasa sederhana: “v tidak bisa dipinjam sebagai mutable karena sedang dipinjam sebagai immutable”.
Ini program yang menghasilkan error tersebut:
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; // referensi ke dalam Vec v.push(4); // ❌ mengubah Vec selagi `first` masih hidup println!("First: {first}"); // ...karena `first` dipakai di sini}Apa artinya
Section titled “Apa artinya”Compiler menunjuk tiga baris, dan kamu butuh ketiganya untuk memahami error ini:
- Baris 3:
&v[0]membuat referensi baca-saja ke dalamv. Itulah immutable borrow (peminjaman yang tidak mengubah). - Baris 4:
v.push(4)perlu mengubahv, jadi ia mengambil mutable borrow (peminjaman untuk mengubah). - Baris 5:
firstdipakai lagi. Baris inilah yang membuat borrow pertama tetap hidup melewati baris 4.
Tanpa baris 5, kodenya akan lolos kompilasi. Masalahnya bukan karena kedua borrow itu ada, tapi karena keduanya tumpang tindih.
Kenapa Rust melakukan ini
Section titled “Kenapa Rust melakukan ini”Aturannya: banyak pembaca atau satu penulis, tidak pernah keduanya sekaligus. Kamu sudah mempelajarinya di halaman Borrowing (peminjaman).
Untuk Vec ada alasan yang sangat konkret. Vec menyimpan item-itemnya dalam satu blok memori. Saat kamu push dan blok itu sudah penuh, Vec memindahkan semuanya ke blok baru yang lebih besar dan membebaskan blok lama. Referensi apa pun ke blok lama (seperti first) sekarang menunjuk ke memori yang sudah dibebaskan. Di C++ ini adalah bug “iterator invalidation” yang terkenal, dan bisa membuat programmu crash atau diam-diam membaca data sampah.
Ibaratnya kamu mencatat alamat seorang teman, lalu ia pindah ke rumah yang lebih besar. Catatanmu masih kelihatan benar, tapi alamat itu menunjuk ke bangunan kosong. Rust menolak mengompilasi kodenya daripada membiarkanmu mendatangi bangunan kosong itu.
Cara memperbaikinya
Section titled “Cara memperbaikinya”Perbaikan 1: Selesaikan membaca sebelum menulis
Section titled “Perbaikan 1: Selesaikan membaca sebelum menulis”Coba ini dulu. Sejak Rust 2018, sebuah borrow berakhir di pemakaian terakhirnya, bukan di kurung kurawal penutup. Ini disebut non-lexical lifetimes. Jadi cukup selesaikan pemakaian referensinya sebelum kamu mengubah vektornya:
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; println!("Pertama: {first}"); // pemakaian terakhir `first`: borrow berakhir di sini v.push(4); // ✅ sudah tidak ada yang membaca println!("Sekarang: {:?}", v);}Output:
Pertama: 1Sekarang: [1, 2, 3, 4]Perbaikan 2: Salin nilainya keluar
Section titled “Perbaikan 2: Salin nilainya keluar”Pilih ini kalau kamu butuh nilai lama setelah perubahan. Ambil salinannya, bukan referensinya. Untuk angka, cukup hapus &-nya. Untuk String, pakai .clone():
fn main() { let mut queue = vec![String::from("deploy"), String::from("test")]; let next = queue[0].clone(); // salinan milik kita sendiri, bukan referensi queue.push(String::from("release")); println!("Berikutnya: {next}"); println!("Antrean: {:?}", queue);}Output:
Berikutnya: deployAntrean: ["deploy", "test", "release"]Dengan let first = v[0]; pada Vec<i32>, kamu langsung mendapat salinan gratis, karena i32 adalah Copy.
Perbaikan 3: Pakai iter_mut atau retain untuk mengubah item di tempat
Section titled “Perbaikan 3: Pakai iter_mut atau retain untuk mengubah item di tempat”Pilih ini kalau kamu menulis loop seperti for p in prices.iter() lalu mencoba prices.remove(i) atau mengubah item di dalamnya. Itu juga menghasilkan E0502. Standard library punya method yang dibuat persis untuk ini:
fn main() { let mut prices = vec![120, 45, 300, 80];
// ubah setiap item di tempat for p in prices.iter_mut() { *p = *p * 9 / 10; // diskon 10% }
// buang item yang tidak kamu inginkan prices.retain(|&p| p >= 50);
println!("{:?}", prices);}Output:
[108, 270, 72]iter_mut memberimu satu &mut per item, jadi hanya pernah ada satu penulis. retain melakukan penghapusan untukmu, dengan aman, dalam satu kali jalan. Lihat Vektor dan Iterator.
Perbaikan 4: Kumpulkan perubahannya, lalu terapkan
Section titled “Perbaikan 4: Kumpulkan perubahannya, lalu terapkan”Pilih ini kalau kamu perlu menambah item berdasarkan apa yang kamu baca. Baca dan kumpulkan dulu, lalu tulis di langkah terpisah:
fn main() { let mut scores = vec![10, 25, 7, 40]; let bonuses: Vec<i32> = scores.iter().filter(|&&s| s > 20).map(|s| s * 2).collect(); scores.extend(bonuses); // borrow untuk membaca sudah selesai, jadi kita boleh menulis println!("{:?}", scores);}Output:
[10, 25, 7, 40, 50, 80]Vec tambahan itu memakan sedikit memori, tapi kodenya jelas dan kelihatan benar. Biasanya itu pertukaran yang bagus.
Perbaikan 5: Pakai indeks
Section titled “Perbaikan 5: Pakai indeks”Pilih ini sebagai jalan terakhir, kalau perbaikan lain tidak cocok. Indeks hanyalah angka, bukan borrow, jadi ia tidak menghalangi perubahan:
fn main() { let mut scores = vec![10, 25, 7]; for i in 0..scores.len() { if scores[i] > 20 { scores.push(scores[i] * 2); } } println!("{:?}", scores);}Output:
[10, 25, 7, 50]0..scores.len() dihitung sekali saja, sebelum loop dimulai, jadi item-item baru tidak ikut dikunjungi. Hati-hati kalau kamu menghapus item lewat indeks: posisinya bergeser, dan kamu bisa melewatkan item atau mengalami panic. Untuk menghapus, retain (Perbaikan 3) lebih aman.
Di mana biasanya kamu melihatnya
Section titled “Di mana biasanya kamu melihatnya”Push ke Vec sambil melakukan loop atasnya. for s in &scores { scores.push(..) } meminjam scores selama seluruh loop. Perbaikannya: kumpulkan dulu lalu extend sesudahnya (Perbaikan 4), atau loop lewat indeks (Perbaikan 5).
Memanggil method &mut self sambil loop atas sebuah field. Ini sangat umum di dalam blok impl:
fn audit(&mut self) { for item in &self.items { self.record(item); // ❌ cannot borrow `*self` as mutable because it is also borrowed as immutable }}self.record meminjam seluruh self sebagai mutable, termasuk self.items. Perbaikannya: akses field lainnya langsung (self.log.push(item.clone())), karena Rust mengizinkan meminjam dua field yang berbeda sekaligus. Atau clone item-itemnya dulu. Lihat Struct.
Menyimpan nilai dari HashMap sambil melakukan insert. let apples = stock.get("apples").unwrap(); lalu stock.insert(..) lalu memakai apples menghasilkan E0502. Perbaikannya: salin nilainya keluar dengan let apples = *stock.get("apples").unwrap(); atau .copied(). Lihat HashMap.
Error terkait
Section titled “Error terkait”- E0499: dua borrow mutable pada saat bersamaan. Idenya sama, hanya saja ada dua penulis, bukan satu pembaca dan satu penulis.
- E0382: kamu memindahkan sebuah nilai lalu memakainya. Sering muncul saat kamu memperbaiki E0502 dengan move alih-alih borrow.
- E0106: lifetime specifier tidak ada pada fungsi yang mengembalikan referensi.
- Semua error compiler: daftar lengkap halaman error.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Kenapa let first = &v[0]; v.push(4); println!("{first}"); tidak bisa dikompilasi?
2. Kamu ingin menghapus setiap item di bawah 50 dari sebuah Vec<i32>. Apa perbaikan E0502 yang paling rapi?
3. Kapan sebuah immutable borrow berakhir di Rust modern?
Skor: 0 / 3
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- E0502 — Rust error codes index (bahasa Inggris): penjelasan resmi error ini.
- Storing Lists of Values with Vectors — The Rust Book (bahasa Inggris): memuat contoh
let first = &v[0]; v.push(..)yang persis sama dan kenapa ia gagal. - References and Borrowing — The Rust Book (bahasa Inggris): aturan “banyak pembaca atau satu penulis” selengkapnya.
- Non-lexical lifetimes — The Edition Guide (bahasa Inggris): kenapa sekarang borrow berakhir di pemakaian terakhirnya, bukan di kurung kurawal penutup.
Vec— dokumentasi standard library (bahasa Inggris):retain,iter_mut,extend, dan method lain yang membuatmu bisa mengubah vektor dengan aman.