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

Lỗi Rust E0596: cannot borrow as mutable (không thể mượn khả biến)

E0596 nghĩa là có đoạn code muốn thay đổi một giá trị (nó cần một lần mượn khả biến, &mut), nhưng bạn chỉ cho nó quyền đọc. Thường là bạn quên mut ở một dòng let, hoặc một hàm nhận & trong khi nó cần &mut. Lỗi này rất hay gặp, và dòng help: của trình biên dịch gần như luôn chỉ đúng chỗ cần thêm 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.

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 needs to change `tasks`
| ^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut tasks = Vec::new();
| +++

Trình biên dịch báo rằng nó không thể mượn tasks theo kiểu khả biến (mutable) vì tasks không được khai báo mut, 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 tasks = Vec::new();
tasks.push("write tests"); // ❌ push needs to change `tasks`
println!("{tasks:?}");
}

Bạn cũng sẽ gặp một cách diễn đạt thứ hai: “cannot borrow *tasks as mutable, as it is behind a & reference” (không thể mượn khả biến vì nó nằm sau một tham chiếu &). Cùng mã lỗi, nguyên nhân hơi khác: ở đó biến là một tham chiếu &, nên bạn không thể thay đổi thứ nó trỏ tới. Cả hai đều được nói đến bên dưới.

Hãy nhìn dấu ^^^^^ dưới tasks ở dòng 3. Để thêm một phần tử, Vec::push cần một lần mượn khả biến của vector: chữ ký của nó là fn push(&mut self, value: T). Nhưng tasks được tạo bằng let trơn, nên nó chỉ đọc được. Rust không thể đưa ra một &mut tới thứ mà bạn đã nói là sẽ không bao giờ thay đổi.

Dòng help: cho thấy cách sửa: let mut tasks. Dấu +++ đánh dấu ba ký tự cần thêm.

Hãy hình dung một tài liệu được chia sẻ với quyền “chỉ xem”. Bạn đọc được từng chữ, nhưng nút “chỉnh sửa” bị mờ đi. mut (hoặc &mut) là cách bạn xin quyền chỉnh sửa.

Trong Rust, biến mặc định là bất biến (immutable), và tham chiếu mặc định chỉ đọc. Muốn thay đổi gì cũng phải nói rõ: let mut cho biến bạn sở hữu, &mut cho tham chiếu tới thứ bạn đang mượn. Xem Biến và Mượn.

Nhờ vậy code dễ tin tưởng hơn. Nếu một hàm nhận &Vec<String>, chỉ cần nhìn chữ ký là bạn biết nó sẽ không thay đổi danh sách của bạn. Nếu nó nhận &mut Vec<String>, bạn biết nó có thể thay đổi. Không có gì thay đổi sau lưng bạn.

Chính quy tắc này giúp Rust có được những đảm bảo an toàn lớn hơn. Vì “có thể thay đổi” luôn được viết ra rõ ràng, trình biên dịch có thể đảm bảo không bao giờ có một bên ghi và một bên đọc cùng lúc (đó là E0502 và E0499). E0596 là bước đầu tiên: bạn phải xin quyền ghi trước khi có ai kiểm tra xem việc ghi có an toàn không.

Chọn cách này khi bạn sở hữu giá trị và nó cần thay đổi: một Vec bạn push vào, một String bạn nối thêm, hay một struct mà bạn gọi phương thức &mut self của nó. Đây là cách sửa đúng trong phần lớn trường hợp.

struct Counter {
count: u32,
}
impl Counter {
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut tasks = Vec::new();
tasks.push("viết test");
tasks.push("sửa lỗi");
println!("{tasks:?}");
let mut counter = Counter { count: 0 };
counter.increment();
counter.increment();
println!("Đếm: {}", counter.count);
}

Kết quả:

["viết test", "sửa lỗi"]
Đếm: 2
▶ Tự chạy thử

Nếu thiếu mut ở counter, counter.increment() sẽ lỗi với cannot borrow counter as mutable. Gọi một phương thức nhận &mut self nghĩa là mượn khả biến cả biến đó, nên biến phải là mut. Những phương thức chỉ nhận &self thì không cần. Xem Thêm hành vi bằng phương thức.

Tham số mà hàm sở hữu cũng vậy: fn shout(s: String) không thể gọi s.push('!'), nhưng fn shout(mut s: String) thì được.

Cách sửa 2: Đổi tham số thành &mut và truyền &mut x

Phần tiêu đề “Cách sửa 2: Đổi tham số thành &mut và truyền &mut x”

Chọn cách này khi một hàm cần thay đổi thứ mà nơi gọi sở hữu. Trường hợp này cho ra cách diễn đạt “behind a & reference”:

fn add_task(tasks: &Vec<String>, task: &str) {
tasks.push(task.to_string()); // ❌ `tasks` is a `&` reference
}
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` is a `&` reference
| ^^^^^ `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) {
| +++

Rust nói tasks chỉ là một tham chiếu & (chỉ đọc), nên không thể mượn nó theo kiểu khả biến. Hãy sửa ở ba chỗ: kiểu tham số thành &mut Vec<String>, biến ở nơi gọi thành let mut, và lời gọi truyền vào &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, "viết test");
add_task(&mut tasks, "cập nhật tài liệu");
println!("{} việc: {tasks:?}", tasks.len());
}

Kết quả:

2 việc: ["viết test", "cập nhật tài liệu"]
▶ Tự chạy thử

Nếu bạn đổi tham số nhưng vẫn truyền &tasks, bạn sẽ gặp E0308, kèm ghi chú types differ in mutability (hai kiểu khác nhau ở tính khả biến). Nó chỉ có nghĩa là: hãy viết &mut tasks ở chỗ gọi nữa. Việc phải viết &mut ở chỗ gọi là có chủ ý; ai đọc main cũng thấy ngay add_task có thể thay đổi tasks.

Chọn cách này khi một closure thay đổi một biến mà nó đã bắt (capture). Closure như vậy thay đổi trạng thái của chính nó mỗi lần chạy, nên gọi nó cần mượn khả biến chính closure đó:

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;
| +++

Biến clicks đã là mut rồi. Thứ còn thiếu là mut cho closure:

fn main() {
let mut clicks = 0;
let mut click = || clicks += 1;
click();
click();
click();
println!("Số lần bấm: {clicks}");
}

Kết quả:

Số lần bấm: 3
▶ Tự chạy thử

Điều này làm nhiều người ngạc nhiên, vì closure trông không “có vẻ” là thay đổi gì. Nhưng bên trong nó giữ một &mut clicks, nên nó là một closure FnMut, và gọi một FnMut cần mut. Xem Closure thay đổi biến đã bắt và Fn, FnMut và FnOnce.

Cách sửa 4: Dùng iter_mut() hoặc &mut trong vòng lặp

Phần tiêu đề “Cách sửa 4: Dùng iter_mut() hoặc &mut trong vòng lặp”

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ử. .iter() và for x in &list đưa ra các tham chiếu &, vốn chỉ đọc:

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

Hãy chuyển sang .iter_mut() hoặc &mut names (và cho chính names là 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:?}");
}

Kết quả:

["ferris!", "corro!"]
[15, 25, 35]
▶ Tự chạy thử

Trình biên dịch cũng gợi ý đúng điều này: help: use mutable method ... names.iter_mut(). Closure trong chuỗi iterator cũng vậy: words.iter().for_each(|w| w.push('!')) lỗi, còn words.iter_mut().for_each(|w| w.push('!')) chạy được. Xem Iterator.

Xây dựng một Vec hoặc String từng bước. let list = Vec::new(); rồi list.push(...), hoặc let s = String::new(); rồi s.push_str(...). Cách sửa: let mut. Nếu bạn có sẵn mọi phần tử ngay từ đầu, let list = vec![a, b, c]; hoàn toàn không cần mut.

Gọi phương thức “setter” hoặc “update” trên một struct. account.deposit(50), với deposit nhận &mut self, sẽ lỗi trừ khi bạn viết let mut account. Cách sửa: let mut account = Account::new();. Xem Struct.

Hàm phụ nhận & theo thói quen. Bạn viết fn reset(board: &Board) rồi cố thay đổi board bên trong. Cách sửa: đổi thành board: &mut Board và gọi reset(&mut board). Một thói quen tốt: viết & khi hàm chỉ đọc, &mut khi hàm thay đổi.

Thay đổi một giá trị bên trong HashMap. if let Some(v) = map.get("key") { v.push(..) } lỗi, vì get trả về một tham chiếu &. Cách sửa: dùng map.get_mut("key"), hoặc API entry. Xem HashMap.

  • E0384: cannot assign twice to immutable variable. Cùng là thiếu mut, nhưng cho x = ... hay x += 1 thay vì gọi phương thức.
  • E0594: không thể gán cho một trường hoặc gán qua một tham chiếu &, ví dụ p.hp = 5; khi p không phải mut, hoặc *x += 1 trong for x in &v. Cách sửa giống nhau: let mut, hoặc &mut.
  • E0308: mismatched types, types differ in mutability, khi bạn truyền &x vào một hàm cần &mut x.
  • E0502: bạn đã có quyền &mut, nhưng có thứ khác vẫn đang đọc giá trị đó.
  • E0499: hai lần mượn &mut cùng một giá trị cùng lúc.
  • 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. let scores = Vec::new(); scores.push(10); gây lỗi E0596. Cách sửa là gì?

2. Hàm fn add(list: &Vec<u32>) gọi list.push(1). Bạn cần đổi gì?

3. let mut total = 0; let add = |x| total += x; add(5); bị lỗi. Vì sao?

Điểm: 0 / 3