Lewati ke konten

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.

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 here
4 | v.push(4); // ❌ mengubah Vec selagi `first` masih hidup
| ^^^^^^^^^ mutable borrow occurs here
5 | println!("First: {first}"); // ...karena `first` dipakai di sini
| ----- immutable borrow later used here

Dalam 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
}

Compiler menunjuk tiga baris, dan kamu butuh ketiganya untuk memahami error ini:

  1. Baris 3: &v[0] membuat referensi baca-saja ke dalam v. Itulah immutable borrow (peminjaman yang tidak mengubah).
  2. Baris 4: v.push(4) perlu mengubah v, jadi ia mengambil mutable borrow (peminjaman untuk mengubah).
  3. Baris 5: first dipakai 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.

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.

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: 1
Sekarang: [1, 2, 3, 4]
▶ Coba sendiri

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: deploy
Antrean: ["deploy", "test", "release"]
▶ Coba sendiri

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]
▶ Coba sendiri

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]
▶ Coba sendiri

Vec tambahan itu memakan sedikit memori, tapi kodenya jelas dan kelihatan benar. Biasanya itu pertukaran yang bagus.

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]
▶ Coba sendiri

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.

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.

  • 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

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