Lỗi Rust E0594: cannot assign, as it is not declared as mutable (gán khi không có mut)
E0594 nghĩa là bạn viết thứ_gì_đó = giá_trị (hoặc +=, -=, …), nhưng bạn chỉ có quyền đọc thứ đó. Thường đó là một trường (field) của struct trên một biến không có mut, hoặc một giá trị bạn truy cập qua một tham chiếu &. Đây là lỗi rất hay gặp, và cách sửa gần như luôn chỉ là một từ: mut. Trang này cho bạn xem cách sửa trước, rồi mới giải thích. Các lỗi khác trong loạt bài này nằm ở lỗi trình biên dịch Rust.
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”error[E0594]: cannot assign to `p.hp`, as `p` is not declared as mutable --> src/main.rs:8:5 |8 | p.hp = 5; // ❌ `p` is read-only | ^^^^^^^^ cannot assign |help: consider changing this to be mutable |7 | let mut p = Player { name: String::from("Ferris"), hp: 10 }; | +++Trình biên dịch báo rằng không thể gán cho p.hp vì p không được khai báo mut, và gợi ý viết let mut p.
Đây là chương trình gây ra lỗi:
struct Player { name: String, hp: u32,}
fn main() { let p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; // ❌ `p` is read-only println!("{} has {} hp", p.name, p.hp);}Chú thích // ❌ giữ tiếng Anh vì nó xuất hiện trong thông báo lỗi; nó nói rằng p chỉ đọc được.
Bạn cũng sẽ thường gặp cách diễn đạt thứ hai: “cannot assign to *x, which is behind a & reference” (không thể gán cho *x vì nó nằm sau một tham chiếu &). Cùng mã lỗi, nguyên nhân hơi khác: ở đó bạn giữ một tham chiếu &, và tham chiếu & chỉ cho phép nhìn. Cả hai đều được nói tới bên dưới.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Hãy nhìn dấu ^^^^^^^^ dưới p.hp = 5 ở dòng 8. Bạn đang thay đổi một trường của p. Nhưng p được tạo bằng let trơn, nên cả struct chỉ đọc được, và điều đó bao gồm mọi trường bên trong nó.
Dòng help: cho thấy cách sửa: let mut p. Dấu +++ đánh dấu ba ký tự cần thêm.
Với cách diễn đạt thứ hai, trình biên dịch chỉ vào một dòng như *x += 1 và thêm `x` is a `&` reference, so it cannot be written to (x là tham chiếu &, nên không thể ghi vào). Bạn không thay đổi một biến bạn sở hữu. Bạn đang cố thay đổi giá trị của người khác qua một tham chiếu mượn, chỉ đọc.
Hãy nghĩ tới một cuốn sách thư viện. Bạn đọc bao nhiêu cũng được, nhưng không được viết vào đó. Muốn ghi chú, bạn cần bản của riêng mình (let mut) hoặc sự cho phép của chủ sở hữu (&mut).
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Trong Rust, mọi thứ đều chỉ đọc trừ khi bạn nói khác. Một biến cần let mut để thay đổi, và một tham chiếu phải là &mut mới thay đổi được thứ nó trỏ tới. Xem Làm cho struct khả biến và Tham chiếu khả biến.
Rust không có “trường khả biến”. Tính khả biến thuộc về biến (hoặc tham chiếu), không thuộc về từng trường riêng lẻ. Vì vậy let mut p làm mọi trường của p thay đổi được, còn let p trơn thì không trường nào thay đổi được. Quy tắc nhờ thế mà đơn giản: nhìn cách một giá trị được khai báo là bạn biết nó có thay đổi được không.
Quy tắc về tham chiếu bảo vệ bạn khỏi những bất ngờ. Nếu bạn truyền &player vào một hàm, chữ ký của nó hứa rằng nó chỉ đọc. Không có lời hứa đó, bất kỳ hàm nào cũng có thể âm thầm thay đổi dữ liệu của bạn, và trình biên dịch không thể ngăn một bên đọc và một bên ghi cùng chạm vào một giá trị cùng lúc (đó là chủ đề của E0502).
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Thêm mut vào biến
Phần tiêu đề “Cách sửa 1: Thêm mut vào biến”Chọn cách này khi bạn sở hữu struct và muốn thay đổi các trường của nó. Đây là cách sửa đúng trong phần lớn trường hợp.
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!("{} còn {} hp", p.name, p.hp);}Kết quả:
Ferris còn 8 hpTuple (pair.0 = 1;) và mảng (grid[2] = 7;) cũng vậy: biến chứa chúng cần mut. Với một struct nằm trong Vec, team[0].hp = 10; cần let mut team; trường hợp đó được báo là E0596, nhưng cách sửa thì giống hệt.
Nếu bạn chỉ gán một lần, có khi bạn chẳng cần mut: hãy tạo struct với giá trị đúng ngay từ đầu, như Player { hp: 5, ..other }.
Cách sửa 2: Lặp bằng &mut v hoặc iter_mut()
Phần tiêu đề “Cách sửa 2: Lặp bằng &mut v hoặc iter_mut()”Chọn cách này khi bạn lặp qua một collection và muốn thay đổi từng phần tử. for x in &v và v.iter() đưa ra các tham chiếu &, vốn chỉ đọc:
fn main() { let scores = vec![10, 20, 30]; for x in &scores { *x += 1; // ❌ `x` is a `&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` is a `&i32` | ^^^^^^^ `x` is a `&` reference, so it cannot be written to |help: use a mutable iterator instead |3 | for x in &mut scores { | +++Rust báo rằng vòng lặp này đưa ra các tham chiếu &, và gợi ý dùng iterator khả biến. Hãy lặp qua &mut scores (hoặc gọi .iter_mut()), và cho chính vector là 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:?}");}Kết quả:
[11, 21, 31][22, 42, 62]&mut scores và scores.iter_mut() làm cùng một việc; chọn cách nào đọc dễ hơn. Nếu bạn không cần thay đổi danh sách tại chỗ, tạo một danh sách mới cũng được: let bigger: Vec<i32> = scores.iter().map(|x| x + 1).collect();. Xem Iterator.
Cách sửa 3: Dùng &mut self hoặc tham số &mut
Phần tiêu đề “Cách sửa 3: Dùng &mut self hoặc tham số &mut”Chọn cách này khi một phương thức hoặc hàm thay đổi dữ liệu được đưa cho nó. Một phương thức với &self chỉ đọc được struct, nên gán cho một trường sẽ lỗi:
impl Account { fn deposit(&self, amount: u64) { self.balance += amount; // ❌ `self` is a `&` reference }}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) { | +++Một hàm bình thường có tham số & cũng gây lỗi tương tự: fn reset(score: &u32) { *score = 0; } lỗi với cannot assign to `*score`, which is behind a `&` reference. Cách sửa cho cả hai là như nhau: ghi &mut trong chữ ký, cho biến ở nơi gọi là let mut, và truyền &mut khi gọi.
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!("Số dư: {}", acc.balance);
let mut score = 42; reset(&mut score); println!("Điểm: {score}");}Kết quả:
Số dư: 75Điểm: 0Với phương thức, Rust tự viết &mut cho bạn ở chỗ gọi: acc.deposit(50) tự mượn khả biến acc, miễn là acc được khai báo let mut. Với hàm, bạn tự viết: reset(&mut score). Nếu bạn truyền &score vào một hàm cần &mut u32, bạn sẽ gặp E0308 (types differ in mutability). Xem Thêm hành vi bằng phương thức.
Cách sửa 4 (phương án cuối): Cell hoặc RefCell
Phần tiêu đề “Cách sửa 4 (phương án cuối): Cell hoặc RefCell”Chỉ chọn cách này khi bạn không thể có &mut: ví dụ bạn cài đặt một trait mà phương thức của nó cố định là &self, hoặc nhiều phần của chương trình cùng dùng chung một giá trị qua Rc. Cell và RefCell cho phép thay đổi qua một tham chiếu & (“interior mutability”, tính khả biến bên trong):
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("táo"), String::from("lê")], lookups: Cell::new(0), history: RefCell::new(Vec::new()), }; println!("lê? {}", catalog.contains("lê")); println!("kiwi? {}", catalog.contains("kiwi")); println!("Số lần tra cứu: {}", catalog.lookups.get()); println!("Lịch sử: {:?}", catalog.history.borrow());}Kết quả:
lê? truekiwi? falseSố lần tra cứu: 2Lịch sử: ["lê", "kiwi"]Cell dành cho các giá trị Copy nhỏ như bộ đếm; RefCell dành cho mọi thứ khác. Hãy thẳng thắn về cái giá: RefCell dời việc kiểm tra mượn từ lúc biên dịch sang lúc chạy, và nó sẽ panic nếu bạn mượn khả biến hai lần cùng lúc. Nếu &mut bình thường dùng được, hãy ưu tiên nó. Giữa nhiều luồng, hãy dùng Mutex hoặc kiểu atomic (xem Trạng thái dùng chung). Đọc thêm ở RefCell.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Thay đổi một giá trị bên trong HashMap. *stock.get("apple").unwrap() += 1; lỗi với cannot assign to data in a `&` reference, vì get trả về một tham chiếu &. Cách sửa: *stock.get_mut("apple").unwrap() += 1;, hoặc *stock.entry("apple").or_insert(0) += 1;. Xem HashMap.
Một closure được truyền dưới dạng Fn mà lại thay đổi biến. run(|| count += 1) với run nhận F: Fn() sẽ lỗi với cannot assign to `count`, as it is a captured variable in a `Fn` closure. Cách sửa: đổi ràng buộc thành F: FnMut() (và mut f bên trong run). Xem Khi bạn chọn sai ràng buộc.
Một bộ đếm toàn cục trong static. static VISITS: u32 = 0; rồi VISITS += 1; lỗi với cannot assign to immutable static item `VISITS`. Cách sửa: dùng static VISITS: AtomicU32 = AtomicU32::new(0); và VISITS.fetch_add(1, Ordering::Relaxed);, hoặc tốt hơn, truyền bộ đếm đi khắp nơi dưới dạng &mut.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0596: cannot borrow as mutable. Cùng là thiếu
mut, nhưng cho một lời gọi phương thức nhưlist.push(x)thay vì phép gán. - E0384: cannot assign twice to immutable variable. Bạn gán cho cả một biến (
x = 5;) chứ không phải một trường hay qua một tham chiếu. - E0308: mismatched types,
types differ in mutability, khi bạn truyền&xở chỗ cần&mut x. - E0502: bạn có quyền
&mut, nhưng có thứ khác vẫn đang đọc giá trị đó. - E0507: cannot move out of a value behind a reference. Lại là quy tắc “chỉ đọc”, lần này cho việc move thay vì ghi.
- Tất cả lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. let user = User { age: 30 }; user.age = 31; gây E0594. Cách sửa là gì?
2. for p in &prices { *p += 1; } bị lỗi. Bạn cần đổi gì?
3. Phương thức fn rename(&self, n: String) { self.name = n; } gây E0594. Cách sửa thường dùng là gì?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0594 — Rust error codes index (tiếng Anh): giải thích chính thức của lỗi này.
- Defining and Instantiating Structs — The Rust Book (tiếng Anh): vì sao cả instance phải khả biến, chứ không phải từng trường.
- References and Borrowing — The Rust Book (tiếng Anh): phần “Mutable References” giải thích
&so với&mut. - Method Syntax — The Rust Book (tiếng Anh): khi nào phương thức cần
&self,&mut self, hoặcself. - RefCell and Interior Mutability — The Rust Book (tiếng Anh):
RefCellcho phép thay đổi qua&như thế nào, và cái giá của nó lúc chạy.