Error Rust E0594: cannot assign, as it is not declared as mutable (tanpa mut)
E0594 artinya kamu menulis sesuatu = nilai (atau +=, -=, …), padahal kamu hanya punya izin untuk membaca sesuatu itu. Biasanya itu field struct pada variabel tanpa mut, atau nilai yang kamu akses lewat referensi &. Error ini sangat umum, dan perbaikannya hampir selalu satu kata: mut. Halaman ini menunjukkan perbaikannya dulu, lalu menjelaskannya. Untuk error lain di seri ini, lihat Error compiler Rust.
Pesan error-nya
Section titled “Pesan error-nya”error[E0594]: cannot assign to `p.hp`, as `p` is not declared as mutable --> src/main.rs:8:5 |8 | p.hp = 5; // ❌ `p` hanya bisa dibaca | ^^^^^^^^ cannot assign |help: consider changing this to be mutable |7 | let mut p = Player { name: String::from("Ferris"), hp: 10 }; | +++Ini program yang menghasilkannya:
struct Player { name: String, hp: u32,}
fn main() { let p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; // ❌ `p` hanya bisa dibaca println!("{} has {} hp", p.name, p.hp);}Kamu juga akan sering melihat kalimat kedua: “cannot assign to *x, which is behind a & reference” (tidak bisa mengisi *x karena berada di balik referensi &). Kode error-nya sama, penyebabnya sedikit berbeda: di sana kamu memegang referensi &, dan referensi & hanya membolehkanmu melihat. Keduanya dibahas di bawah.
Apa artinya
Section titled “Apa artinya”Lihat tanda ^^^^^^^^ di bawah p.hp = 5 di baris 8. Kamu sedang mengubah sebuah field dari p. Tapi p dibuat dengan let biasa, jadi seluruh struct-nya hanya bisa dibaca, termasuk setiap field di dalamnya.
Baris help: menunjukkan perbaikannya: let mut p. Tanda +++ menandai tiga karakter yang perlu ditambahkan.
Pada kalimat kedua, compiler menunjuk baris seperti *x += 1 dan menambahkan `x` is a `&` reference, so it cannot be written to (x adalah referensi &, jadi tidak bisa ditulisi). Kamu tidak sedang mengubah variabel milikmu. Kamu mencoba mengubah nilai milik orang lain lewat referensi pinjaman yang hanya bisa dibaca.
Bayangkan buku perpustakaan. Kamu boleh membacanya sepuasnya, tapi tidak boleh mencoret-coretnya. Untuk menulis catatan, kamu butuh salinanmu sendiri (let mut) atau izin dari pemiliknya (&mut).
Kenapa Rust melakukan ini
Section titled “Kenapa Rust melakukan ini”Di Rust, semuanya hanya bisa dibaca kecuali kamu bilang sebaliknya. Sebuah variabel butuh let mut agar bisa berubah, dan sebuah referensi harus &mut agar bisa mengubah nilai yang ditunjuknya. Lihat Membuat struct menjadi mutable dan Referensi mutable.
Tidak ada “field mutable” di Rust. Mutability (sifat bisa diubah) dimiliki oleh variabel (atau referensinya), bukan oleh field satu per satu. Jadi let mut p membuat setiap field p bisa diubah, dan let p biasa membuat tidak ada satu pun yang bisa diubah. Aturannya jadi sederhana: lihat bagaimana sebuah nilai dideklarasikan, dan kamu tahu apakah nilai itu bisa berubah.
Aturan referensi melindungimu dari kejutan. Kalau kamu mengirim &player ke sebuah fungsi, signature-nya berjanji fungsi itu hanya akan membaca. Tanpa janji itu, fungsi mana pun bisa diam-diam mengubah datamu, dan compiler tidak bisa mencegah pembaca dan penulis menyentuh nilai yang sama pada waktu yang sama (itulah inti dari E0502).
Cara memperbaikinya
Section titled “Cara memperbaikinya”Perbaikan 1: Tambahkan mut ke variabel
Section titled “Perbaikan 1: Tambahkan mut ke variabel”Pilih ini kalau struct-nya milikmu dan kamu ingin mengubah field-nya. Ini perbaikan yang tepat di sebagian besar kasus.
struct Player { name: String, hp: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; p.hp += 3; println!("{} punya {} hp", p.name, p.hp);}Output:
Ferris punya 8 hpHal yang sama berlaku untuk tuple (pair.0 = 1;) dan array (grid[2] = 7;): variabel yang menyimpannya butuh mut. Untuk struct di dalam Vec, team[0].hp = 10; butuh let mut team; kasus itu dilaporkan sebagai E0596, tapi perbaikannya sama.
Kalau kamu hanya mengisi sekali, mungkin kamu sama sekali tidak butuh mut: buat struct-nya dengan nilai yang benar dari awal, seperti Player { hp: 5, ..other }.
Perbaikan 2: Lakukan perulangan dengan &mut v atau iter_mut()
Section titled “Perbaikan 2: Lakukan perulangan dengan &mut v atau iter_mut()”Pilih ini kalau kamu melakukan perulangan atas sebuah koleksi dan ingin mengubah setiap item. for x in &v dan v.iter() memberikan referensi &, yang hanya bisa dibaca:
fn main() { let scores = vec![10, 20, 30]; for x in &scores { *x += 1; // ❌ `x` adalah `&i32` } println!("{scores:?}");}error[E0594]: cannot assign to `*x`, which is behind a `&` reference --> src/main.rs:4:9 |3 | for x in &scores { | ------- this iterator yields `&` references4 | *x += 1; // ❌ `x` adalah `&i32` | ^^^^^^^ `x` is a `&` reference, so it cannot be written to |help: use a mutable iterator instead |3 | for x in &mut scores { | +++Compiler menjelaskan bahwa iterator ini menghasilkan referensi &, lalu menyarankan iterator yang mutable. Lakukan perulangan atas &mut scores (atau panggil .iter_mut()), dan jadikan vektornya sendiri mut:
fn main() { let mut scores = vec![10, 20, 30]; for x in &mut scores { *x += 1; } println!("{scores:?}");
for x in scores.iter_mut() { *x *= 2; } println!("{scores:?}");}Output:
[11, 21, 31][22, 42, 62]&mut scores dan scores.iter_mut() melakukan hal yang sama; pilih mana yang lebih enak dibaca. Kalau kamu tidak perlu mengubah daftarnya di tempat, membuat daftar baru juga bisa: let bigger: Vec<i32> = scores.iter().map(|x| x + 1).collect();. Lihat Iterator.
Perbaikan 3: Pakai &mut self atau parameter &mut
Section titled “Perbaikan 3: Pakai &mut self atau parameter &mut”Pilih ini kalau sebuah method atau fungsi mengubah data yang diberikan kepadanya. Method dengan &self hanya bisa membaca struct-nya, jadi mengisi sebuah field akan gagal:
impl Account { fn deposit(&self, amount: u64) { self.balance += amount; // ❌ `self` adalah referensi `&` }}error[E0594]: cannot assign to `self.balance`, which is behind a `&` reference --> src/main.rs:7:9 |7 | self.balance += amount; | ^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so it cannot be written to |help: consider changing this to be a mutable reference |6 | fn deposit(&mut self, amount: u64) { | +++Di sini self adalah referensi &, jadi tidak bisa ditulisi, dan compiler menyarankan &mut self. Fungsi biasa dengan parameter & memberi error yang sama: fn reset(score: &u32) { *score = 0; } gagal dengan cannot assign to `*score`, which is behind a `&` reference. Perbaikannya sama untuk keduanya: tulis &mut di signature, jadikan variabel milik pemanggil let mut, dan kirim &mut saat memanggil.
struct Account { balance: u64,}
impl Account { fn deposit(&mut self, amount: u64) { self.balance += amount; }}
fn reset(score: &mut u32) { *score = 0;}
fn main() { let mut acc = Account { balance: 0 }; acc.deposit(50); acc.deposit(25); println!("Saldo: {}", acc.balance);
let mut score = 42; reset(&mut score); println!("Skor: {score}");}Output:
Saldo: 75Skor: 0Untuk method, Rust menuliskan &mut untukmu saat memanggil: acc.deposit(50) otomatis meminjam acc secara mutable, asalkan acc dideklarasikan let mut. Untuk fungsi, kamu menulisnya sendiri: reset(&mut score). Kalau kamu mengirim &score ke fungsi yang meminta &mut u32, kamu akan mendapat E0308 (types differ in mutability). Lihat Menambahkan perilaku dengan method.
Perbaikan 4 (pilihan terakhir): Cell atau RefCell
Section titled “Perbaikan 4 (pilihan terakhir): Cell atau RefCell”Pilih ini hanya kalau kamu tidak bisa mendapatkan &mut: misalnya, kamu mengimplementasikan trait yang method-nya sudah pasti &self, atau banyak bagian program berbagi satu nilai lewat Rc. Cell dan RefCell membolehkan perubahan lewat referensi & (“interior mutability”, mutability dari dalam):
use std::cell::{Cell, RefCell};
struct Catalog { items: Vec<String>, lookups: Cell<u32>, history: RefCell<Vec<String>>,}
impl Catalog { fn contains(&self, name: &str) -> bool { self.lookups.set(self.lookups.get() + 1); self.history.borrow_mut().push(name.to_string()); self.items.iter().any(|item| item == name) }}
fn main() { let catalog = Catalog { items: vec![String::from("apple"), String::from("pear")], lookups: Cell::new(0), history: RefCell::new(Vec::new()), }; println!("pear? {}", catalog.contains("pear")); println!("kiwi? {}", catalog.contains("kiwi")); println!("Jumlah pencarian: {}", catalog.lookups.get()); println!("Riwayat: {:?}", catalog.history.borrow());}Output:
pear? truekiwi? falseJumlah pencarian: 2Riwayat: ["pear", "kiwi"]Cell cocok untuk nilai Copy yang kecil seperti penghitung; RefCell untuk yang lainnya. Tapi ada harganya: RefCell memindahkan pemeriksaan borrow dari waktu kompilasi ke waktu program berjalan, dan ia akan panic kalau kamu meminjam secara mutable dua kali sekaligus. Kalau &mut biasa sudah cukup, pakai itu saja. Antar-thread, pakai Mutex atau atomic (lihat Shared state). Selengkapnya di RefCell.
Di mana biasanya kamu melihatnya
Section titled “Di mana biasanya kamu melihatnya”Mengubah nilai di dalam HashMap. *stock.get("apple").unwrap() += 1; gagal dengan cannot assign to data in a `&` reference, karena get mengembalikan referensi &. Perbaikan: *stock.get_mut("apple").unwrap() += 1;, atau *stock.entry("apple").or_insert(0) += 1;. Lihat HashMap.
Closure yang dikirim sebagai Fn tapi mengubah variabel. run(|| count += 1) dengan run yang menerima F: Fn() gagal dengan cannot assign to `count`, as it is a captured variable in a `Fn` closure. Perbaikan: ubah bound-nya menjadi F: FnMut() (dan mut f di dalam run). Lihat Kalau kamu salah memilih bound.
Penghitung global di static. static VISITS: u32 = 0; lalu VISITS += 1; gagal dengan cannot assign to immutable static item `VISITS`. Perbaikan: pakai static VISITS: AtomicU32 = AtomicU32::new(0); dan VISITS.fetch_add(1, Ordering::Relaxed);, atau lebih baik lagi, kirim penghitungnya ke mana-mana sebagai &mut.
Error terkait
Section titled “Error terkait”- E0596: cannot borrow as mutable (tidak bisa meminjam sebagai mutable). Sama-sama kurang
mut, tapi untuk pemanggilan method sepertilist.push(x), bukan pengisian nilai. - E0384: cannot assign twice to immutable variable. Kamu mengisi ulang seluruh variabel (
x = 5;), bukan sebuah field atau lewat referensi. - E0308: mismatched types (tipe tidak cocok),
types differ in mutability, saat kamu mengirim&xdi tempat yang meminta&mut x. - E0502: kamu punya akses
&mut, tapi ada bagian lain yang masih membaca nilainya. - E0507: cannot move out of a value behind a reference. Aturan “hanya-baca” lagi, kali ini untuk move, bukan untuk menulis.
- Semua error compiler: daftar lengkap halaman error.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. let user = User { age: 30 }; user.age = 31; memberi E0594. Apa perbaikannya?
2. for p in &prices { *p += 1; } gagal. Apa yang kamu ubah?
3. Method fn rename(&self, n: String) { self.name = n; } memberi E0594. Apa perbaikan yang biasa?
Skor: 0 / 3
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- E0594 — Rust error codes index (bahasa Inggris): penjelasan resmi error ini.
- Defining and Instantiating Structs — The Rust Book (bahasa Inggris): kenapa seluruh instance harus mutable, bukan field satu per satu.
- References and Borrowing — The Rust Book (bahasa Inggris): bagian “Mutable References” menjelaskan
&dibandingkan&mut. - Method Syntax — The Rust Book (bahasa Inggris): kapan sebuah method butuh
&self,&mut self, atauself. - RefCell and Interior Mutability — The Rust Book (bahasa Inggris): bagaimana
RefCellmembolehkan perubahan lewat&, dan biayanya saat program berjalan.