Lỗi Rust E0597: borrowed value does not live long enough (sống không đủ lâu)
E0597 nghĩa là: bạn giữ một tham chiếu (&x) lâu hơn chính giá trị x. Giá trị bị dọn dẹp ở cuối khối (block) của nó, nhưng có thứ gì đó bên ngoài khối vẫn muốn dùng tham chiếu. Đây là lỗi rất phổ biến, nhất là với khối lồng nhau, vòng lặp và struct, và cách sửa thường chỉ là dời một dòng code hoặc lưu một giá trị sở hữu (owned) thay vì một tham chiếu. (Trang này thuộc giải thích các 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[E0597]: `x` does not live long enough --> src/main.rs:5:13 |4 | let x = 5; | - binding `x` declared here5 | r = &x; // r borrows x | ^^ borrowed value does not live long enough6 | } // x is dropped here | - `x` dropped here while still borrowed7 | println!("r: {r}"); // ❌ r would point at nothing | - borrow later used hereTrình biên dịch nói rằng x “không sống đủ lâu”: x bị hủy ở cuối khối trong khi r vẫn còn mượn nó và còn được dùng ở dòng 7.
Đây là chương trình gây ra lỗi:
fn main() { let r; { let x = 5; r = &x; // r borrows x } // x is dropped here println!("r: {r}"); // ❌ r would point at nothing}Các chú thích trong code giữ tiếng Anh vì chúng xuất hiện nguyên văn trong thông báo lỗi: r mượn x, x bị hủy ở cuối khối, và r sẽ trỏ vào hư không.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Bốn nhãn kể lại câu chuyện theo thứ tự:
- Dòng 4:
xđược tạo bên trong khối lồng{ ... }. - Dòng 5:
rmượnx. Đến đây vẫn ổn. - Dòng 6: khối kết thúc, nên
xbị drop (dọn dẹp). Lần mượn vẫn còn sống. - Dòng 7:
rđược dùng, nhưng giá trị nó trỏ tới đã biến mất.
Hãy tưởng tượng bạn ghi lại số phòng khách sạn của một người bạn. Số đó hữu ích khi bạn ấy còn ở đó. Khi bạn ấy trả phòng, con số vẫn còn trên tờ giấy của bạn, nhưng giờ nó dẫn tới một căn phòng trống, hoặc phòng của người lạ. Rust không cho bạn giữ một tờ giấy dẫn tới căn phòng trống.
Bạn cũng sẽ thấy nhãn ngắn “borrowed value does not live long enough” (giá trị được mượn không sống đủ lâu). Đó vẫn là lỗi này; dòng tiêu đề chỉ ghi thêm tên biến.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Một tham chiếu không bao giờ được sống lâu hơn giá trị nó trỏ tới. Nếu có, bạn sẽ có một tham chiếu treo (dangling reference): một con trỏ tới vùng nhớ đã được giải phóng và giờ có thể chứa thứ khác. Trong C và C++, đó là nguồn gốc kinh điển của crash và lỗ hổng bảo mật (“use after free”).
Mọi giá trị trong Rust bị drop khi chủ sở hữu của nó ra khỏi phạm vi (scope), thường là ở dấu } đóng khối nơi nó được khai báo. Borrow checker so sánh hai khoảng: giá trị sống bao lâu và tham chiếu được dùng bao lâu. Nếu tham chiếu được dùng sau dấu } của giá trị, bạn gặp E0597. Xem Phạm vi, Mượn và Lifetime.
Để ý điều trình biên dịch không phàn nàn: việc mượn x ở dòng 5. Vấn đề chỉ nằm ở lần dùng sau đó ở dòng 7. Bỏ dòng cuối đi là chương trình biên dịch được. Đó là gợi ý hữu ích cho các cách sửa bên dưới: hoặc làm giá trị sống lâu hơn, hoặc làm cho việc dùng tham chiếu kết thúc sớm hơn.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Đưa giá trị ra phạm vi bên ngoài
Phần tiêu đề “Cách sửa 1: Đưa giá trị ra phạm vi bên ngoài”Chọn cách này khi giá trị thật sự cần tồn tại lâu bằng tham chiếu. Đây là cách sửa phổ biến nhất: khai báo giá trị trước (bên ngoài) khối, để nó sống ít nhất lâu bằng tham chiếu.
fn main() { let scores = vec![72, 95, 88]; // giờ sống đến hết main let best; { best = scores.iter().max().unwrap(); } println!("Điểm cao nhất: {best}"); // ✅ scores vẫn còn sống}Kết quả:
Điểm cao nhất: 95Quy tắc dễ nhớ: chủ sở hữu phải được khai báo trước bên mượn và sống lâu hơn nó. Nếu bạn thấy một giá trị được tạo bên trong một khối hoặc vòng lặp, còn tham chiếu tới nó lại được dùng bên ngoài, hãy đưa giá trị ra ngoài.
Cách sửa 2: Lưu giá trị sở hữu thay vì tham chiếu
Phần tiêu đề “Cách sửa 2: Lưu giá trị sở hữu thay vì tham chiếu”Chọn cách này khi bạn gom các giá trị được tạo bên trong vòng lặp. Mỗi lượt lặp tạo một String mới và drop nó ở cuối lượt, nên một Vec<&str> bên ngoài vòng lặp không thể giữ tham chiếu tới chúng:
fn main() { let mut names: Vec<&str> = Vec::new(); for i in 1..=3 { let name = format!("player{i}"); names.push(&name); // ❌ `name` does not live long enough } println!("{names:?}");}Cách sửa là để Vec sở hữu các chuỗi. Đổi Vec<&str> thành Vec<String> và đẩy chính String vào (thao tác này move nó vào vector, nên nó sống lâu bằng vector):
fn main() { let mut names: Vec<String> = Vec::new(); for i in 1..=3 { let name = format!("player{i}"); names.push(name); // move String vào Vec } println!("{names:?}");}Kết quả:
["player1", "player2", "player3"]Ý tưởng này cũng áp dụng cho struct và HashMap: nếu dữ liệu được tạo trong một phạm vi nhỏ hơn container, hãy lưu String (hoặc Vec<T>) thay vì &str (hoặc &[T]). Với người mới, trường sở hữu (owned field) gần như luôn là lựa chọn đúng. Xem Chuỗi và Vector.
Cách sửa 3: Clone nó
Phần tiêu đề “Cách sửa 3: Clone nó”Chọn cách này khi bạn vẫn cần dùng tiếp bản gốc, hoặc bạn chỉ có một tham chiếu mượn tới nó. Một struct chứa tham chiếu (Label<'a> { text: &'a str }) không thể sống lâu hơn chuỗi nó trỏ tới. Hãy cho struct một trường String sở hữu và lưu một bản sao:
struct Label { text: String, // sở hữu, không phải &str}
fn main() { let label; { let title = String::from("Hộp thư đến"); label = Label { text: title.clone() }; println!("Bên trong khối: {title}"); } println!("Nhãn: {}", label.text); // ✅ label sở hữu bản sao của riêng nó}Kết quả:
Bên trong khối: Hộp thư đếnNhãn: Hộp thư đếnNếu sau đó bạn không cần title nữa, bỏ clone và move nó luôn: Label { text: title }. Clone một chuỗi ngắn rất rẻ và hoàn toàn ổn. Nếu bạn chỉ có &str, dùng .to_string() để có một bản sao sở hữu.
Cách sửa 4: Sắp xếp lại để lần mượn kết thúc kịp lúc
Phần tiêu đề “Cách sửa 4: Sắp xếp lại để lần mượn kết thúc kịp lúc”Chọn cách này khi giá trị vốn chỉ cần sống ngắn. Thay vì làm giá trị sống lâu hơn, hãy dùng tham chiếu khi giá trị vẫn còn sống. Trường hợp này hay gặp với các hàm trả về tham chiếu, như ví dụ longest kinh điển:
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { let first = String::from("Ferris the crab"); { let second = String::from("Corro"); let winner = longest(&first, &second); println!("Tên dài nhất: {winner}"); // ✅ dùng trước khi second bị drop }}Kết quả:
Tên dài nhất: Ferris the crabHai cái tên được giữ tiếng Anh vì longest so sánh độ dài theo byte (.len()), mà chữ tiếng Việt có dấu chiếm nhiều byte hơn.
Nếu winner được khai báo bên ngoài khối và in ra sau khối, bạn sẽ gặp E0597 cho second, dù first tình cờ dài hơn. Chữ ký hàm nói kết quả có thể mượn từ bất kỳ đối số nào, nên cả hai phải sống lâu hơn kết quả. Nếu bạn thật sự cần kết quả về sau, hãy lưu nó dưới dạng sở hữu: let winner = longest(&first, &second).to_string();.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Một closure được lưu bên ngoài khối. Closure dùng name sẽ mượn nó. Nếu closure sống lâu hơn name, bạn gặp E0597 kèm một nhãn phụ, value captured here (giá trị được bắt ở đây):
let say_hello;{ let name = String::from("Ferris"); say_hello = || println!("Hello, {name}!"); // ❌ `name` does not live long enough}say_hello();Cách sửa: viết move || ... để closure lấy quyền sở hữu name. Xem Closure.
Luồng (thread). Mượn một biến cục bộ bên trong thread::spawn(|| ...) cho ra một lỗi khác, E0373 (“closure may outlive the current function”), vì luồng có thể chạy lâu hơn main. Cách sửa: thread::spawn(move || ...), chia sẻ bằng Arc, hoặc dùng std::thread::scope. Xem Đồng thời.
Tham chiếu tới một giá trị tạm thời. let label = Label { text: String::from("Inbox").as_str() }; gần giống E0597 nhưng hiện ra dưới dạng E0716, “temporary value dropped while borrowed”: String không bao giờ được lưu vào biến, nên nó bị drop ở cuối câu lệnh. Cách sửa: đưa nó vào một biến trước (let title = String::from("Inbox");), hoặc cho trường đó là String.
Trả về tham chiếu từ một hàm. Nếu giá trị được tạo bên trong hàm, bạn sẽ gặp E0515 thay vì lỗi này. Hãy trả về giá trị sở hữu.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0515: cannot return reference to local variable: cùng vấn đề nhưng vượt qua ranh giới hàm.
- E0373: closure may outlive the current function: phiên bản luồng của lỗi này. Thường sửa bằng
move. - E0106: missing lifetime specifier: Rust cần biết một tham chiếu được trả về hoặc được lưu đang mượn từ đâu.
- E0382: borrow of moved value: cái bẫy ngược lại, dùng một giá trị sau khi đã trao nó đi.
- E0502: mutable borrow while immutably borrowed: một tham chiếu vẫn đang được dùng khi bạn cố thay đổi giá trị.
E0716: temporary value dropped while borrowed: giống E0597, nhưng cho một giá trị chưa bao giờ được lưu vào biến.- Giải thích tất cả lỗi trình biên dịch
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Trong let r; { let x = 5; r = &x; } rồi một println! in r, điều gì thật sự gây ra E0597?
2. Mỗi lượt lặp bạn tạo một String và đẩy &name vào một Vec<&str>. Cách sửa thường dùng là gì?
3. Closure của một luồng mượn một Vec cục bộ. Bạn gặp lỗi nào?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0597 — Rust error codes index (tiếng Anh): giải thích chính thức của lỗi này.
- The Borrow Checker — The Rust Book (tiếng Anh): cùng ví dụ
rvàx, kèm sơ đồ hai lifetime. - Borrowing — Rust by Example (tiếng Anh): các ví dụ ngắn chạy được về mượn và việc mượn kéo dài bao lâu.
- Lifetimes — Rust by Example (tiếng Anh): trình biên dịch nhìn vòng đời của từng giá trị và từng lần mượn như thế nào.
- E0716 — Rust error codes index (tiếng Anh): lỗi rất gần “temporary value dropped while borrowed”.