Khắc phục lỗi Rust E0521: Khi dữ liệu mượn thoát khỏi Closure

intermediate🦀 Rust2026-07-27| Rustc 1.50+ (Mọi hệ điều hành: Linux, Windows, macOS)

Error Message

error[E0521]: borrowed data escapes outside of closure
#rust#closure#lifetime#borrow-checker#ownership

Sự bế tắc gây ức chế trong Pipeline

Bạn đang xây dựng một pipeline dữ liệu hiệu suất cao và logic có vẻ rất ổn. Bạn có một closure xử lý một tham chiếu và cố gắng gán nó cho một biến ở phạm vi bên ngoài. Sau đó, trình biên dịch báo lỗi. Thay vì một tệp thực thi, bạn nhận được một thông báo khó hiểu về việc một "internal closure slot" bị thoát ra ngoài.

error[E0521]: borrowed data escapes outside of closure
  --> src/main.rs:10:9
   |
8  |     let mut shared_data = "";
9  |     process_data(|input| {
10 |         shared_data = input;
   |         ^^^^^^^^^^^^^^^^^^^ slot closure nội bộ đã thoát ra tại đây

Điều này thường xảy ra khi sử dụng iterator, thread pool hoặc các thư viện như serde. Trình biên dịch đang bảo vệ bạn khỏi con trỏ lơ lửng (dangling pointer), nhưng lỗi này khiến bạn cảm thấy như nó đang chặn một đoạn mã hoàn toàn hợp lệ.

Tại sao Borrow Checker lại can thiệp

Lỗi E0521 về cơ bản là sự không khớp về lifetime (vòng đời). Khi một closure nhận một đối số như |input: &str|, Rust coi tham chiếu đó chỉ có giá trị trong thời gian thực thi của closure. Đó là một khoản vay tạm thời sẽ hết hạn ngay khi closure kết thúc.

Nếu bạn gán input cho shared_data, bạn đang cố gắng chuyển một tham chiếu ngắn hạn vào một phạm vi có tuổi thọ dài hơn nó. Trình biên dịch coi đây là một vi phạm an toàn. Nó không thể đảm bảo rằng dữ liệu mà input trỏ tới vẫn tồn tại sau khi closure kết thúc. Điều này thường gặp với Higher-Rank Trait Bounds (HRTBs), nơi chữ ký hàm về cơ bản nói với closure rằng: "Bạn có thể xem dữ liệu này, nhưng không thể giữ nó."

Cách khắc phục nhanh: Chuyển quyền sở hữu (Ownership)

Giải pháp đơn giản nhất là ngừng phụ thuộc vào tham chiếu. Nếu bạn chuyển đổi dữ liệu mượn thành một kiểu sở hữu (owned type), chẳng hạn như String hoặc Vec, bạn sẽ phá vỡ sự phụ thuộc vào lifetime. Bạn không còn mượn một giá trị tạm thời nữa; bạn đang tạo ra một giá trị mới tồn tại lâu như bạn cần.

Trước:

let mut result: &str = "";
some_function(|data| {
    result = data; // Lỗi E0521: data bị hủy khi closure kết thúc
});

Sau:

let mut result: String = String::new();
some_function(|data| {
    result = data.to_string(); // result hiện đã sở hữu bản sao dữ liệu của riêng nó
});

Việc gọi .to_string() hoặc .to_owned() sẽ cấp phát bộ nhớ trên heap. Mặc dù việc này làm tăng một chút chi phí hiệu suất—thường là vài trăm nano giây cho các chuỗi ngắn—nhưng nó sẽ làm hài lòng borrow checker ngay lập tức.

Các giải pháp kiến trúc để tối ưu hiệu suất

Nếu ứng dụng của bạn xử lý hàng triệu mục mỗi giây, việc cấp phát heap bên trong một vòng lặp có thể quá chậm. Hãy xem xét các lựa chọn thay thế về cấu trúc sau:

1. Sử dụng Scoped Threads

Nếu bạn gặp lỗi này khi sử dụng std::thread::spawn, trình biên dịch lo ngại rằng thread có thể sống lâu hơn các biến cục bộ. Kể từ Rust 1.63, bạn có thể sử dụng std::thread::scope. Điều này chứng minh với trình biên dịch rằng closure sẽ kết thúc trước khi phạm vi bên ngoài bị hủy, cho phép bạn chia sẻ các tham chiếu một cách an toàn.

2. Atomic Reference Counting (Arc)

Khi dữ liệu phải được chia sẻ giữa nhiều thread mà không cần clone liên tục, hãy bao bọc nó trong một Arc<T>. Việc clone một Arc chỉ làm tăng bộ đếm tham chiếu (một số nguyên 64-bit) thay vì sao chép toàn bộ cấu trúc dữ liệu. Cách này rẻ hơn đáng kể so với việc clone một Vec lớn hoặc một struct phức tạp.

use std::sync::Arc;

let heavy_data = Arc::new(String::from("10MB dữ liệu..."));
let data_ptr = Arc::clone(&heavy_data);

// Từ khóa 'move' chuyển quyền sở hữu con trỏ vào trong closure
thread::spawn(move || {
    println!("{}", data_ptr);
});

Xác minh giải pháp

Sau khi áp dụng bản sửa lỗi, hãy đảm bảo giải pháp không gây ra các nút thắt cổ chai tiềm ẩn:

- **Xác nhận bằng `cargo check`:** Cách này nhanh chóng xác nhận rằng borrow checker đã hài lòng mà không cần đợi build toàn bộ.
- **Theo dõi cấp phát bộ nhớ:** Nếu bạn chọn cách `.to_string()`, hãy sử dụng các công cụ như `heaptrack` hoặc `valgrind`. Đảm bảo bạn không thấy mức sử dụng bộ nhớ tăng đột biến trong các đường dẫn quan trọng (hot paths).
- **Kiểm tra với Miri:** Nếu dự án của bạn sử dụng các khối `unsafe`, hãy chạy `cargo miri test`. Nó sẽ phát hiện nếu các thay đổi về quyền sở hữu của bạn vô tình gây ra hành vi không xác định (undefined behavior).

Lỗi Rust E0521 không phải là lỗi trong logic của bạn; đó là trình biên dịch đang yêu cầu một sự đảm bảo. Hãy cung cấp cho dữ liệu đó một "ngôi nhà" vĩnh viễn thông qua quyền sở hữu (ownership), và bạn sẽ có thể tiếp tục triển khai mã nguồn của mình.

Related Error Notes