Fix lỗi error[E0502]: cannot borrow as mutable because it is also borrowed as immutable trong Rust

intermediate🦀 Rust2026-03-17| Rust 1.x (tất cả phiên bản), mọi hệ điều hành — Linux, macOS, Windows

Error Message

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
#rust#borrow#ownership#lifetime

TL;DR

Bạn đang giữ một tham chiếu bất biến (&x) trong khi cũng cố thay đổi x thông qua &mut x. Rust không cho phép cả hai tồn tại cùng lúc. Hãy kết thúc mượn bất biến trước khi bắt đầu mượn khả biến. Trong thực tế, điều đó có nghĩa là tái cấu trúc code để tránh các tham chiếu chồng chéo — hoặc clone dữ liệu bạn cần ngay từ đầu.

Lỗi đầy đủ

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:5
  |
4 |     let r = &x;          // mượn bất biến bắt đầu ở đây
5 |     println!("{}", r);
6 |     x.push(4);           // mượn khả biến được thực hiện ở đây
  |     ^^^^^^^^^ mutable borrow occurs here
7 |     println!("{}", r);   // immutable borrow later used here
  |                    - immutable borrow later used here

Borrow checker thực thi một quy tắc cơ bản: hoặc nhiều tham chiếu bất biến, hoặc một tham chiếu khả biến — không bao giờ cả hai cùng lúc. Đây không phải hạn chế tùy tiện. Đây là cơ chế giúp Rust đảm bảo an toàn bộ nhớ mà không cần garbage collector.

Nguyên nhân gốc rễ

Ngay khi bạn viết let r = &x, Rust coi x là đang được mượn bất biến trong suốt thời gian r còn được sử dụng. Gọi x.push(4) sau đó tạo ra xung đột — một phần code đang giữ chế độ xem chỉ đọc trong khi phần khác cố thay đổi dữ liệu bên dưới. Trong C++ hay Java, điều này âm thầm gây ra iterator invalidation hoặc data race. Rust từ chối biên dịch nó.

Mượn kéo dài đến lần dùng cuối của tham chiếu — không phải đến cuối khối. Quy tắc đó gọi là Non-Lexical Lifetimes (NLL), được ổn định trong Rust 2018. Nó hỗ trợ rất nhiều, nhưng không thể cứu bạn nếu bạn thực sự dùng tham chiếu bất biến sau khi thay đổi.

Cách sửa 1 — Dừng dùng tham chiếu bất biến trước khi thay đổi

Cách sửa đơn giản nhất: tái cấu trúc code để mượn bất biến kết thúc trước khi thay đổi xảy ra.

fn main() {
    let mut x = vec![1, 2, 3];

    // Dùng tham chiếu xong rồi để nó bị drop
    {
        let r = &x;
        println!("trước: {:?}", r);
    } // <-- r ra khỏi phạm vi ở đây

    x.push(4); // giờ an toàn để thay đổi
    println!("sau: {:?}", x);
}

Không cần thêm khối nếu bạn không dùng r sau khi thay đổi — NLL tự động kết thúc mượn tại lần dùng cuối:

fn main() {
    let mut x = vec![1, 2, 3];
    let r = &x;
    println!("trước: {:?}", r);
    // r không được dùng sau dòng này, nên mượn kết thúc ở đây (NLL)

    x.push(4);
    println!("sau: {:?}", x);
}

Cách sửa 2 — Clone dữ liệu cần thiết trước khi thay đổi

Đôi khi bạn thực sự cần giá trị cũ sau khi thay đổi. Hãy clone nó ngay từ đầu — một bản sao độc lập không có mượn nào trên x.

fn main() {
    let mut x = vec![1, 2, 3];
    let snapshot = x.clone(); // bản sao độc lập, không mượn x

    x.push(4);

    println!("cũ: {:?}", snapshot);
    println!("mới: {:?}", x);
}

Cách sửa 3 — Dùng chỉ số thay vì tham chiếu

Code dùng Vec thường xuyên gặp E0502: lấy tham chiếu đến một phần tử, rồi thử push phần tử khác. Compiler chặn lại — ngay cả khi push không bao giờ chạm đến phần tử bạn tham chiếu. Dùng chỉ số giúp tránh hoàn toàn vấn đề này.

// Sai: lưu tham chiếu vào vec
fn bad_example(items: &mut Vec<String>) {
    let first = &items[0];   // mượn bất biến
    items.push("new".to_string()); // mượn khả biến — LỖI BIÊN DỊCH
    println!("{}", first);
}

// Đúng: lưu chỉ số (hoặc bản clone), không phải tham chiếu
fn good_example(items: &mut Vec<String>) {
    let first = items[0].clone(); // String độc lập, không giữ mượn
    items.push("new".to_string()); // ổn
    println!("{}", first);
}

Cách sửa 4 — Interior mutability với RefCell (single-threaded)

Cấu trúc graph, cache và hệ thống sự kiện đôi khi cần truy cập khả biến dùng chung mà borrow checker tĩnh không thể phân tích được. RefCell<T> xử lý điều này bằng cách chuyển kiểm tra mượn từ compile time sang runtime.

use std::cell::RefCell;

fn main() {
    let x = RefCell::new(vec![1, 2, 3]);

    let r = x.borrow(); // mượn bất biến (runtime)
    println!("trước: {:?}", *r);
    drop(r);            // giải phóng tường minh trước khi thay đổi

    x.borrow_mut().push(4); // giờ ổn rồi
    println!("sau: {:?}", x.borrow());
}

Một lưu ý: nếu hai mượn xung đột tại runtime, RefCell sẽ panic thay vì gây ra undefined behaviour. Đối với code đa luồng, hãy dùng Arc<Mutex<T>> thay thế.

Cách sửa 5 — Tách struct thành các field riêng biệt

E0502 bên trong phương thức struct thường có nghĩa là Rust không thể thấy rằng hai field là độc lập. Hãy mượn từng field riêng lẻ thay vì mượn cả self.

struct Game {
    players: Vec<String>,
    log: Vec<String>,
}

impl Game {
    fn record_join(&mut self, name: &str) {
        // Mượn hai field riêng biệt — compile được
        let players = &self.players;
        let entry = format!("đã tham gia: {}, tổng: {}", name, players.len());
        self.log.push(entry); // thay đổi log, không phải players — OK
    }
}

Mượn &self toàn bộ rồi yêu cầu &mut self trông như một xung đột với compiler, ngay cả khi các field thực sự không chồng chéo. Truy cập trực tiếp vào self.playersself.log giúp thể hiện sự độc lập một cách tường minh.

Kiểm tra sau khi sửa

Thông báo lỗi của Rust chính là bộ kiểm tra của bạn. Bắt đầu từ đây:

cargo check

Không có E0502 nghĩa là bạn đã xong. Sau đó xác nhận hành vi chưa thay đổi:

cargo test

Đã thêm .clone()? Chạy Clippy nữa:

cargo clippy

Clippy gắn cờ các clone không cần thiết. Với vec hoặc string lớn, một clone thừa có thể âm thầm trở thành vấn đề hiệu suất nghiêm trọng.

Hướng dẫn quyết định nhanh

  • Các tham chiếu thực ra không chồng chéo tại runtime? → Tái cấu trúc để mượn bất biến kết thúc trước (cách sửa 1).
  • Cần snapshot của giá trị cũ? → Clone nó (cách sửa 2).
  • Đang index một collection đồng thời cũng sửa đổi nó? → Lưu chỉ số, không lưu tham chiếu (cách sửa 3).
  • Shared ownership hoặc cấu trúc graph?RefCell hoặc Arc<Mutex> (cách sửa 4).
  • Xung đột bên trong phương thức struct? → Mượn từng field riêng lẻ (cách sửa 5).

Đọc thêm

Related Error Notes