Bỏ qua để đến nội dung

Lỗi Rust E0384: cannot assign twice to immutable variable (gán lại biến)

E0384 nghĩa là bạn đã cho một biến một giá trị, rồi lại cố cho nó một giá trị khác, nhưng biến đó không được khai báo với mut. Trong Rust, biến không thể thay đổi trừ khi bạn nói rõ là cho phép. Hầu như ai cũng gặp lỗi này trong giờ đầu tiên học Rust, và cách sửa thường chỉ là ba chữ cái: mut. Trang này cho bạn xem cách sửa trước, rồi giải thích vì sao Rust làm vậy. Các lỗi khác trong loạt bài này nằm ở lỗi trình biên dịch 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 is not mut
| ^^^^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut score = 10;
| +++

Trình biên dịch báo rằng bạn gán giá trị lần thứ hai cho score, một biến bất biến (immutable), và gợi ý thêm mut vào dòng 2.

Đây là chương trình gây ra lỗi:

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

Nếu biến là một tham số hàm, câu chữ hơi khác một chút: “cannot assign to immutable argument” (không thể gán cho tham số bất biến). Vẫn là cùng mã lỗi và cùng một ý.

Trình biên dịch chỉ vào hai chỗ:

  1. Dòng 2, có ghi first assignment to score (lần gán đầu tiên cho score): đây là nơi score nhận giá trị. Vì dòng này viết let chứ không phải let mut, giá trị đó là cố định.
  2. Dòng 4, có dấu ^^^^: đây là nơi bạn cố đặt giá trị mới vào. Rust từ chối.

Phần help: đã cho thấy cách sửa: let mut score = 10;. Dấu +++ đánh dấu ba ký tự cần thêm.

Hãy coi let như viết bằng bút mực, còn let mut như viết bằng bút chì. Cả hai đều ổn. Nhưng đã viết bằng bút mực thì sau này bạn không tẩy được.

Quy tắc này cũng áp dụng cho +=, -=, *= và các toán tử tương tự. count += 1 cũng là một phép gán, nên nó cũng cần mut.

Trong Rust, mọi biến đều bất biến theo mặc định. Bạn phải chủ động cho phép thay đổi bằng mut. Điều này được giải thích ở trang Biến.

Nghe thì có vẻ khắt khe, nhưng nó giúp bạn rất nhiều. Khi đọc let total = ..., bạn biết total giữ nguyên giá trị đó đến hết phạm vi (scope) của nó. Bạn không cần dò phần còn lại của hàm để xem có chỗ nào đổi nó không. Còn khi thấy let mut, đó là một tín hiệu rõ ràng: “để ý biến này, nó sẽ thay đổi”.

Nó còn bắt được lỗi thật. Nếu bạn định đổi một bản sao nhưng lỡ ghi vào bản gốc, hoặc vô tình dùng lại một cái tên, trình biên dịch sẽ chặn bạn lại. Và Rust cũng cảnh báo những biến mut không bao giờ thay đổi (variable does not need to be mutable), nên tín hiệu này luôn đáng tin theo cả hai chiều.

Chọn cách này khi biến thật sự cần thay đổi theo thời gian: một bộ đếm, một tổng cộng dồn, một điểm số, một trạng thái. Đây là cách sửa đúng trong phần lớn trường hợp, và cũng chính là điều trình biên dịch gợi ý.

fn main() {
let mut score = 10;
println!("Điểm: {score}");
score = 15; // ✅ giờ thì được phép
println!("Điểm mới: {score}");
let mut count = 0;
for _ in 0..3 {
count += 1;
}
println!("Đếm: {count}");
}

Kết quả:

Điểm: 10
Điểm mới: 15
Đếm: 3
▶ Tự chạy thử

Bộ đếm là ví dụ kinh điển: let count = 0; rồi count += 1; trong vòng lặp sẽ cho lỗi E0384 ở dòng +=. Thêm mut là xong. Xem Cho phép biến thay đổi với mut.

Cách sửa 2: Dùng shadowing để có giá trị mới

Phần tiêu đề “Cách sửa 2: Dùng shadowing để có giá trị mới”

Chọn cách này khi bạn không thật sự muốn thay đổi biến. Bạn muốn một giá trị mới được tính từ giá trị cũ, thường qua vài bước, và sau đó nó giữ nguyên. Hãy viết let lần nữa với cùng tên:

fn main() {
let price = 20;
let price = price + 5; // một `price` mới, tạo từ `price` cũ
let price = price * 2;
println!("Giá cuối cùng: {price}");
let input = " 42 ";
let input = input.trim(); // vẫn là văn bản
let input: u32 = input.parse().unwrap(); // giờ là một số
println!("Đã đọc được: {}", input + 1);
}

Kết quả:

Giá cuối cùng: 50
Đã đọc được: 43
▶ Tự chạy thử

Cách này gọi là shadowing (che khuất): mỗi let tạo ra một biến hoàn toàn mới, che đi biến cũ. Nó mang lại hai điều hay. price cuối cùng là bất biến, nên sau này không gì có thể vô tình thay đổi nó. Và kiểu cũng có thể đổi, như input chuyển từ &str sang u32, điều mà mut không làm được. Đọc thêm ở Shadowing: dùng lại cùng một tên.

Một quy tắc đơn giản: nếu giá trị thay đổi liên tục (trong vòng lặp, theo thời gian), hãy dùng mut. Nếu bạn đang chuẩn bị một giá trị qua vài bước, shadowing đọc rất gọn.

Chọn cách này khi một hàm muốn thay đổi bản sao đối số của riêng nó. Tham số cũng bất biến, nên đoạn code này lỗi:

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) {
| +++

Rust báo rằng tham số n là bất biến và gợi ý viết mut n: u32. Hãy đặt mut trước tên tham số:

fn countdown(mut n: u32) {
while n > 0 {
println!("{n}...");
n -= 1;
}
println!("Phóng!");
}
fn main() {
let start = 3;
countdown(start);
println!("start vẫn là {start}");
}

Kết quả:

3...
2...
1...
Phóng!
start vẫn là 3
▶ Tự chạy thử

Để ý rằng start trong main không hề thay đổi. mut n chỉ làm cho bản sao riêng của hàm có thể thay đổi; nơi gọi hàm không cần mut và không bị ảnh hưởng. Nếu bạn muốn hàm thay đổi biến của nơi gọi, hãy nhận &mut u32 và truyền vào &mut start. Xem Hàm và Mượn.

Thay đổi biến của vòng lặp for. Biến vòng lặp là một biến mới, bất biến, ở mỗi lượt lặp:

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

Cách sửa: hiếm khi bạn thật sự muốn đổi chính biến đếm. Hãy bỏ qua lượt bằng continue, dùng .step_by(2), hoặc nếu cần kiểm soát hoàn toàn thì chuyển sang vòng lặp while với let mut i. (for mut i in 0..5 biên dịch được, nhưng nó chỉ đổi bản sao của lượt hiện tại; nó không bỏ qua gì cả.) Xem break và continue và vòng lặp while.

Khai báo trước, gán sau. let total; không có giá trị là hợp lệ, và bạn được gán nó một lần. Lần gán thứ hai sẽ gây E0384:

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

Cách sửa: nếu nó thật sự thay đổi, dùng let mut total. Nếu bạn chỉ muốn chọn một trong hai giá trị, hãy tính nó trong một biểu thức: let total = if big_order { 6 } else { 5 };. Xem if / else.

Cộng dồn một tổng. let sum = 0; rồi sum += price; trong vòng lặp là trường hợp E0384 phổ biến nhất. Cách sửa: let mut sum = 0;, hoặc bỏ luôn biến đó và viết let sum: u32 = prices.iter().sum();. Xem Iterator.

  • E0596: cannot borrow as mutable, as it is not declared as mutable (không thể mượn khả biến vì biến không được khai báo mut). Cùng vấn đề “quên mut”, nhưng khi gọi một phương thức làm thay đổi giá trị như .push() thay vì gán.
  • E0594: cannot assign to p.hp, as p is not declared as mutable. Bạn gặp lỗi này khi gán cho một trường của struct (p.hp = 5;) hoặc ghi qua một tham chiếu &. Cách sửa: let mut p, hoặc một tham chiếu &mut. Xem Làm cho struct khả biến.
  • E0502: bạn cố thay đổi một giá trị trong khi nó đang được mượn để đọc.
  • E0382: bạn dùng một giá trị sau khi nó đã bị move.
  • Tất cả lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.

Kiểm tra nhanh

1. Bạn viết let lives = 3; rồi sau đó lives -= 1;. Vì sao bạn gặp E0384?

2. Bạn muốn làm sạch name một lần (cắt khoảng trắng và viết hoa) rồi không bao giờ đổi nữa. Cách nào đọc dễ nhất?

3. fn halve(n: u32) thực hiện n /= 2; và gặp E0384. Cách sửa nào giữ nguyên phía nơi gọi hàm?

Điểm: 0 / 3