Lewati ke konten

Error Rust E0384: cannot assign twice to immutable variable (diisi dua kali)

E0384 artinya kamu sudah memberi sebuah variabel nilai, lalu mencoba memberinya nilai lain, padahal variabel itu tidak dideklarasikan dengan mut. Di Rust, variabel tidak bisa diubah kecuali kamu sendiri yang bilang boleh. Hampir semua orang kena error ini di jam pertama belajar Rust, dan perbaikannya biasanya cuma tiga huruf: mut. Halaman ini menunjukkan perbaikannya dulu, lalu menjelaskan kenapa Rust bekerja seperti ini. Untuk error lain di seri ini, lihat Error compiler Rust.

error[E0384]: cannot assign twice to immutable variable `score`
--> src/main.rs:4:5
|
2 | let score = 10;
| ----- first assignment to `score`
3 | println!("Score: {score}");
4 | score = 15; // ❌ score tidak mut
| ^^^^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut score = 10;
| +++

Ini program yang menghasilkannya:

fn main() {
let score = 10;
println!("Score: {score}");
score = 15; // ❌ score tidak mut
println!("New score: {score}");
}

Kalau variabelnya adalah parameter fungsi, kalimatnya sedikit berbeda: “cannot assign to immutable argument” (tidak bisa mengisi ulang argumen yang immutable). Kode error-nya sama, idenya juga sama.

Compiler menunjuk dua tempat:

  1. Baris 2, ditandai first assignment to score (pengisian pertama score): di sinilah score mendapat nilainya. Karena barisnya ditulis let, bukan let mut, nilai itu sudah final.
  2. Baris 4, ditandai ^^^^: di sinilah kamu mencoba memasukkan nilai baru. Rust menolaknya.

Bagian help: sudah menunjukkan perbaikannya: let mut score = 10;. Tanda +++ menandai tiga karakter yang perlu ditambahkan.

Bayangkan let itu menulis dengan pulpen, dan let mut menulis dengan pensil. Dua-duanya boleh. Tapi kalau kamu menulis dengan pulpen, kamu tidak bisa menghapusnya nanti.

Aturan yang sama berlaku untuk +=, -=, *= dan teman-temannya. count += 1 juga termasuk pengisian nilai, jadi juga butuh mut.

Di Rust, setiap variabel immutable secara default (tidak dapat diubah). Kamu harus memilih sendiri agar bisa diubah dengan mut. Ini dijelaskan di halaman Variabel.

Kedengarannya ketat, tapi ini sangat membantu. Saat kamu membaca let total = ..., kamu tahu total akan tetap bernilai itu sampai akhir scope-nya. Kamu tidak perlu menelusuri sisa fungsi untuk memastikan tidak ada yang mengubahnya. Dan saat kamu melihat let mut, itu tanda yang jelas: “perhatikan yang ini, nilainya berubah”.

Ini juga menangkap bug sungguhan. Kalau kamu berniat mengubah salinan tapi tidak sengaja menulis ke aslinya, atau kamu memakai ulang sebuah nama secara tidak sengaja, compiler akan menghentikanmu. Rust juga memberi peringatan untuk variabel mut yang tidak pernah berubah (variable does not need to be mutable), jadi tanda mut tetap jujur dari dua arah.

Pilih ini kalau variabelnya memang seharusnya berubah dari waktu ke waktu: penghitung, total berjalan, skor, atau status. Ini perbaikan yang tepat di sebagian besar kasus, dan persis inilah yang disarankan compiler.

fn main() {
let mut score = 10;
println!("Skor: {score}");
score = 15; // ✅ sekarang boleh
println!("Skor baru: {score}");
let mut count = 0;
for _ in 0..3 {
count += 1;
}
println!("Jumlah: {count}");
}

Output:

Skor: 10
Skor baru: 15
Jumlah: 3
▶ Coba sendiri

Penghitung adalah contoh klasiknya: let count = 0; lalu count += 1; di dalam perulangan memberi E0384 di baris +=. Menambahkan mut memperbaikinya. Lihat Membuat variabel bisa diubah dengan mut.

Perbaikan 2: Pakai shadowing untuk nilai baru

Section titled “Perbaikan 2: Pakai shadowing untuk nilai baru”

Pilih ini kalau kamu sebenarnya tidak ingin mengubah variabelnya. Kamu ingin nilai baru yang dihitung dari nilai lama, sering kali dalam beberapa langkah, dan setelah itu nilainya tetap. Tulis let lagi dengan nama yang sama:

fn main() {
let price = 20;
let price = price + 5; // `price` baru, dibuat dari yang lama
let price = price * 2;
println!("Harga akhir: {price}");
let input = " 42 ";
let input = input.trim(); // masih berupa teks
let input: u32 = input.parse().unwrap(); // sekarang berupa angka
println!("Hasil parse: {}", input + 1);
}

Output:

Harga akhir: 50
Hasil parse: 43
▶ Coba sendiri

Ini disebut shadowing (menimpa nama): setiap let membuat variabel yang benar-benar baru dan menyembunyikan yang lama. Ada dua keuntungan. price yang terakhir immutable, jadi tidak ada yang bisa mengubahnya secara tidak sengaja nanti. Dan tipenya bisa berubah, seperti input yang berubah dari &str menjadi u32, sesuatu yang tidak bisa dilakukan mut. Selengkapnya di Shadowing: memakai ulang nama yang sama.

Aturan sederhananya: kalau nilainya berubah terus-menerus (di dalam perulangan, seiring waktu), pakai mut. Kalau kamu sedang menyiapkan sebuah nilai dalam beberapa langkah, shadowing lebih enak dibaca.

Perbaikan 3: Tambahkan mut ke parameter fungsi

Section titled “Perbaikan 3: Tambahkan mut ke parameter fungsi”

Pilih ini kalau sebuah fungsi ingin mengubah salinan argumennya sendiri. Parameter juga immutable, jadi kode ini gagal:

fn countdown(n: u32) {
while n > 0 {
println!("{n}...");
n -= 1; // ❌ cannot assign to immutable argument `n`
}
println!("Liftoff!");
}
error[E0384]: cannot assign to immutable argument `n`
--> src/main.rs:4:9
|
4 | n -= 1; // ❌ cannot assign to immutable argument `n`
| ^^^^^^ cannot assign to immutable argument
|
help: consider making this binding mutable
|
1 | fn countdown(mut n: u32) {
| +++

Compiler menunjuk n -= 1 dan menyarankan mut n di parameter. Taruh mut di depan nama parameternya:

fn countdown(mut n: u32) {
while n > 0 {
println!("{n}...");
n -= 1;
}
println!("Meluncur!");
}
fn main() {
let start = 3;
countdown(start);
println!("start masih {start}");
}

Output:

3...
2...
1...
Meluncur!
start masih 3
▶ Coba sendiri

Perhatikan bahwa start di main tidak berubah. mut n hanya membuat salinan milik fungsi itu sendiri bisa diubah; pemanggilnya tidak perlu mut dan tidak terpengaruh. Kalau kamu ingin fungsinya mengubah variabel milik pemanggil, terima &mut u32 dan kirim &mut start. Lihat Fungsi dan Borrowing.

Mengubah variabel perulangan for. Variabel perulangan adalah variabel baru yang immutable di setiap putaran:

for i in 0..5 {
if i == 2 {
i += 1; // ❌ cannot assign twice to immutable variable `i`
}
println!("{i}");
}

Perbaikan: kamu jarang benar-benar ingin mengubah penghitung perulangannya. Lompati putaran dengan continue, pakai .step_by(2), atau kalau kamu butuh kendali penuh, ganti dengan perulangan while dan let mut i. (for mut i in 0..5 memang bisa dikompilasi, tapi itu hanya mengubah salinan di putaran itu; tidak ada yang terlewati.) Lihat break dan continue dan perulangan while.

Mendeklarasikan dulu, mengisi belakangan. let total; tanpa nilai itu boleh, dan kamu bisa mengisinya sekali. Pengisian kedua memberi E0384:

let total;
total = 5;
total = 6; // ❌ cannot assign twice to immutable variable `total`

Perbaikan: kalau nilainya memang berubah, pakai let mut total. Kalau kamu sebenarnya hanya ingin memilih satu nilai, hitung dalam satu ekspresi: let total = if big_order { 6 } else { 5 };. Lihat if / else.

Mengumpulkan total. let sum = 0; lalu sum += price; di dalam perulangan adalah E0384 yang paling sering terjadi. Perbaikan: let mut sum = 0;, atau lewati variabelnya dan tulis let sum: u32 = prices.iter().sum();. Lihat Iterator.

  • E0596: cannot borrow as mutable, as it is not declared as mutable (tidak bisa meminjam sebagai mutable). Masalah “lupa mut” yang sama, tapi untuk memanggil method yang mengubah seperti .push(), bukan untuk mengisi nilai.
  • E0594: cannot assign to p.hp, as p is not declared as mutable. Kamu melihat ini saat mengisi field struct (p.hp = 5;) atau menulis lewat referensi &. Perbaikan: let mut p, atau referensi &mut. Lihat Membuat struct menjadi mutable.
  • E0502: kamu mencoba mengubah sebuah nilai saat nilai itu sedang dipinjam untuk dibaca.
  • E0382: kamu memakai sebuah nilai setelah nilai itu dipindah (move).
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. Kamu menulis let lives = 3; lalu kemudian lives -= 1;. Kenapa kamu mendapat E0384?

2. Kamu ingin merapikan name sekali saja (trim lalu jadikan huruf besar) dan tidak pernah mengubahnya lagi. Mana yang paling enak dibaca?

3. fn halve(n: u32) menjalankan n /= 2; dan mendapat E0384. Perbaikan apa yang membuat pemanggilnya tetap sama?

Skor: 0 / 3