Lewati ke konten

Error Rust E0596: cannot borrow as mutable, as it is not declared as mutable

E0596 artinya ada kode yang ingin mengubah sebuah nilai (ia butuh mutable borrow, &mut), tapi kamu hanya memberinya izin untuk membaca. Biasanya kamu lupa mut di sebuah let, atau sebuah fungsi menerima & padahal ia butuh &mut. Error ini sangat umum, dan baris help: dari compiler hampir selalu memberi tahu persis di mana kamu harus menambahkan mut. Halaman ini menunjukkan perbaikannya dulu, lalu menjelaskannya. Untuk error lain di seri ini, lihat Error compiler Rust.

error[E0596]: cannot borrow `tasks` as mutable, as it is not declared as mutable
--> src/main.rs:3:5
|
3 | tasks.push("write tests"); // ❌ push perlu mengubah `tasks`
| ^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut tasks = Vec::new();
| +++

Ini program yang menghasilkannya:

fn main() {
let tasks = Vec::new();
tasks.push("write tests"); // ❌ push perlu mengubah `tasks`
println!("{tasks:?}");
}

Kamu juga akan melihat kalimat kedua: “cannot borrow *tasks as mutable, as it is behind a & reference” (tidak bisa meminjam sebagai mutable karena berada di balik referensi &). Kode error-nya sama, penyebabnya sedikit berbeda: di sana variabelnya adalah referensi &, jadi kamu tidak bisa mengubah nilai yang ditunjuknya. Keduanya dibahas di bawah.

Lihat tanda ^^^^^ di bawah tasks di baris 3. Untuk menambahkan item, Vec::push butuh mutable borrow (peminjaman yang bisa mengubah) dari vektornya: signature-nya fn push(&mut self, value: T). Tapi tasks dibuat dengan let biasa, jadi ia hanya bisa dibaca. Rust tidak bisa memberikan &mut untuk sesuatu yang kamu nyatakan tidak akan pernah berubah.

Baris help: menunjukkan perbaikannya: let mut tasks. Tanda +++ menandai tiga karakter yang perlu ditambahkan.

Bayangkan dokumen bersama dengan akses “hanya lihat”. Kamu bisa membaca setiap katanya, tapi tombol “edit” berwarna abu-abu. mut (atau &mut) adalah caramu meminta akses edit.

Di Rust, variabel immutable secara default, dan referensi juga hanya-baca secara default. Mengubah apa pun butuh izin yang ditulis jelas: let mut untuk variabel milikmu, &mut untuk referensi ke sesuatu yang kamu pinjam. Lihat Variabel dan Borrowing.

Ini membuat kode lebih mudah dipercaya. Kalau sebuah fungsi menerima &Vec<String>, dari signature-nya saja kamu tahu fungsi itu tidak akan mengubah daftarmu. Kalau ia menerima &mut Vec<String>, kamu tahu fungsi itu mungkin mengubahnya. Tidak ada yang berubah diam-diam di belakangmu.

Aturan yang sama juga memungkinkan jaminan keamanan Rust yang lebih besar. Karena “bisa berubah” selalu tertulis, compiler bisa memastikan tidak pernah ada penulis dan pembaca pada saat yang sama (itulah E0502 dan E0499). E0596 adalah langkah pertamanya: kamu harus meminta izin menulis sebelum ada yang memeriksa apakah menulis itu aman.

Pilih ini kalau nilainya milikmu dan memang harus berubah: Vec yang kamu push, String yang kamu tambahi teks, atau struct yang method &mut self-nya kamu panggil. Ini perbaikan yang tepat di sebagian besar kasus.

struct Counter {
count: u32,
}
impl Counter {
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut tasks = Vec::new();
tasks.push("write tests");
tasks.push("fix bug");
println!("{tasks:?}");
let mut counter = Counter { count: 0 };
counter.increment();
counter.increment();
println!("Jumlah: {}", counter.count);
}

Output:

["write tests", "fix bug"]
Jumlah: 2
▶ Coba sendiri

Tanpa mut di counter, counter.increment() gagal dengan cannot borrow counter as mutable. Memanggil method yang menerima &mut self meminjam seluruh variabel secara mutable, jadi variabelnya harus mut. Method yang hanya menerima &self tetap jalan tanpa mut. Lihat Menambahkan perilaku dengan method.

Hal yang sama berlaku untuk parameter yang dimiliki sebuah fungsi: fn shout(s: String) tidak bisa memanggil s.push('!'), tapi fn shout(mut s: String) bisa.

Perbaikan 2: Ubah parameter menjadi &mut dan kirim &mut x

Section titled “Perbaikan 2: Ubah parameter menjadi &mut dan kirim &mut x”

Pilih ini kalau sebuah fungsi perlu mengubah sesuatu milik pemanggilnya. Versi ini memberi kalimat “behind a & reference”:

fn add_task(tasks: &Vec<String>, task: &str) {
tasks.push(task.to_string()); // ❌ `tasks` adalah referensi `&`
}
error[E0596]: cannot borrow `*tasks` as mutable, as it is behind a `&` reference
--> src/main.rs:2:5
|
2 | tasks.push(task.to_string()); // ❌ `tasks` adalah referensi `&`
| ^^^^^ `tasks` is a `&` reference, so it cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
1 | fn add_task(tasks: &mut Vec<String>, task: &str) {
| +++

Compiler bilang tasks hanyalah referensi &, jadi tidak bisa dipinjam sebagai mutable, dan menyarankan &mut Vec<String>. Perbaiki di tiga tempat: tipe parameternya menjadi &mut Vec<String>, variabel milik pemanggil menjadi let mut, dan pemanggilannya mengirim &mut tasks:

fn add_task(tasks: &mut Vec<String>, task: &str) {
tasks.push(task.to_string());
}
fn main() {
let mut tasks = Vec::new();
add_task(&mut tasks, "write tests");
add_task(&mut tasks, "update docs");
println!("{} tugas: {tasks:?}", tasks.len());
}

Output:

2 tugas: ["write tests", "update docs"]
▶ Coba sendiri

Kalau kamu mengubah parameternya tapi tetap mengirim &tasks, kamu akan mendapat E0308, dengan catatan types differ in mutability (tipenya berbeda dalam hal mutability). Artinya cuma: tulis juga &mut tasks saat memanggil. Menulis &mut di tempat pemanggilan itu disengaja; siapa pun yang membaca main bisa melihat bahwa add_task mungkin mengubah tasks.

Pilih ini kalau sebuah closure mengubah variabel yang ditangkapnya. Closure seperti itu mengubah state-nya sendiri setiap kali dijalankan, jadi memanggilnya butuh mutable borrow dari closure itu sendiri:

fn main() {
let mut clicks = 0;
let click = || clicks += 1;
click(); // ❌ cannot borrow `click` as mutable
click();
println!("Clicks: {clicks}");
}
error[E0596]: cannot borrow `click` as mutable, as it is not declared as mutable
--> src/main.rs:3:9
|
3 | let click = || clicks += 1;
| ^^^^^ ------ calling `click` requires mutable binding due to mutable borrow of `clicks`
| |
| not mutable
4 | click(); // ❌ cannot borrow `click` as mutable
| ----- cannot borrow as mutable
5 | click();
| ----- cannot borrow as mutable
|
help: consider changing this to be mutable
|
3 | let mut click = || clicks += 1;
| +++

Pesannya bilang: memanggil click butuh variabel yang mutable, karena closure itu meminjam clicks secara mutable. Variabel clicks sudah mut. Yang kurang adalah mut di closure-nya:

fn main() {
let mut clicks = 0;
let mut click = || clicks += 1;
click();
click();
click();
println!("Klik: {clicks}");
}

Output:

Klik: 3
▶ Coba sendiri

Ini mengejutkan banyak orang, karena closure-nya tidak “terlihat” seperti berubah. Tapi di dalamnya ia memegang &mut clicks, jadi ia adalah closure FnMut, dan memanggil FnMut butuh mut. Lihat Closure yang mengubah variabel tangkapannya dan Fn, FnMut, dan FnOnce.

Perbaikan 4: Pakai iter_mut() atau &mut di perulangan

Section titled “Perbaikan 4: Pakai iter_mut() atau &mut di perulangan”

Pilih ini kalau kamu melakukan perulangan atas sebuah koleksi dan ingin mengubah setiap item. .iter() dan for x in &list memberikan referensi &, yang hanya bisa dibaca:

for name in names.iter() {
name.push('!'); // ❌ cannot borrow `*name` as mutable, as it is behind a `&` reference
}

Ganti dengan .iter_mut() atau &mut names (dan jadikan names sendiri mut):

fn main() {
let mut names = vec![String::from("ferris"), String::from("corro")];
for name in names.iter_mut() {
name.push('!');
}
println!("{names:?}");
let mut prices = vec![10, 20, 30];
for price in &mut prices {
*price += 5;
}
println!("{prices:?}");
}

Output:

["ferris!", "corro!"]
[15, 25, 35]
▶ Coba sendiri

Compiler juga menyarankan ini: help: use mutable method ... names.iter_mut(). Hal yang sama berlaku untuk closure di rantai iterator: words.iter().for_each(|w| w.push('!')) gagal, words.iter_mut().for_each(|w| w.push('!')) berhasil. Lihat Iterator.

Membangun Vec atau String sedikit demi sedikit. let list = Vec::new(); lalu list.push(...), atau let s = String::new(); lalu s.push_str(...). Perbaikan: let mut. Kalau semua item sudah ada dari awal, let list = vec![a, b, c]; sama sekali tidak butuh mut.

Memanggil method “setter” atau “update” pada struct. account.deposit(50) dengan deposit yang menerima &mut self akan gagal kecuali kamu menulis let mut account. Perbaikan: let mut account = Account::new();. Lihat Struct.

Fungsi pembantu yang menerima & karena kebiasaan. Kamu menulis fn reset(board: &Board) lalu mencoba mengubah board di dalamnya. Perbaikan: ubah menjadi board: &mut Board dan panggil reset(&mut board). Kebiasaan yang baik: tulis & kalau fungsinya hanya membaca, &mut kalau ia mengubah.

Mengubah nilai di dalam HashMap. if let Some(v) = map.get("key") { v.push(..) } gagal, karena get mengembalikan referensi &. Perbaikan: pakai map.get_mut("key"), atau API entry. Lihat HashMap.

  • E0384: cannot assign twice to immutable variable (variabel immutable diisi dua kali). Sama-sama kurang mut, tapi untuk x = ... atau x += 1, bukan pemanggilan method.
  • E0594: tidak bisa mengisi field atau menulis lewat referensi &, misalnya p.hp = 5; saat p tidak mut, atau *x += 1 di dalam for x in &v. Perbaikannya sama: let mut, atau &mut.
  • E0308: mismatched types (tipe tidak cocok), types differ in mutability, saat kamu mengirim &x ke fungsi yang meminta &mut x.
  • E0502: kamu sudah mendapat akses &mut, tapi ada bagian lain yang masih membaca nilainya.
  • E0499: dua borrow &mut dari nilai yang sama pada waktu yang sama.
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. let scores = Vec::new(); scores.push(10); memberi E0596. Apa perbaikannya?

2. Fungsi fn add(list: &Vec<u32>) memanggil list.push(1). Apa yang kamu ubah?

3. let mut total = 0; let add = |x| total += x; add(5); gagal. Kenapa?

Skor: 0 / 3