Lỗi Rust E0434: can't capture dynamic environment in a fn item (biến cục bộ)
E0434 nghĩa là một hàm được viết bên trong một hàm khác đã cố dùng một biến cục bộ của hàm bên ngoài. Trông có vẻ như phải chạy được: biến nằm ngay đó, cách vài dòng phía trên. Nhưng trong Rust, một fn lồng bên trong là một mục (item) riêng biệt, và nó không nhìn thấy các biến xung quanh. Cách diễn đạt của trình biên dịch, “dynamic environment” (môi trường động), chỉ có nghĩa là “các biến cục bộ tồn tại trong lúc hàm bên ngoài chạy”. Đây là một trong những lỗi dễ sửa nhất: rustc nói luôn đáp án trong dòng help:. (Trang này thuộc loạt bài 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[E0434]: can't capture dynamic environment in a fn item --> src/main.rs:5:24 |5 | price * (1.0 + tax_rate) // ❌ inner `fn` can't see `tax_rate` | ^^^^^^^^ | = help: use the `|| { ... }` closure form insteadThông báo của trình biên dịch luôn bằng tiếng Anh: “can’t capture dynamic environment in a fn item” nghĩa là “một mục fn không thể bắt (capture) môi trường động”. Đây là chương trình gây ra lỗi đó (chú thích được giữ bằng tiếng Anh vì nó xuất hiện nguyên văn trong thông báo lỗi):
fn main() { let tax_rate = 0.2;
fn with_tax(price: f64) -> f64 { price * (1.0 + tax_rate) // ❌ inner `fn` can't see `tax_rate` }
println!("{:.2}", with_tax(10.0));}Ý nghĩa
Phần tiêu đề “Ý nghĩa”Dấu ^^^ chỉ vào tax_rate ở dòng 5. Đó là một biến cục bộ của main, được tạo bằng let ở dòng 2. Hàm bên trong with_tax cố đọc nó, và điều đó không được phép.
Dòng help: đưa ra cách sửa: hãy viết một closure, || { ... }, thay cho một fn lồng. Closure có thể dùng các biến xung quanh nó. Còn fn thì không, dù bạn viết nó ở đâu.
Bạn cũng thường gặp thêm một cảnh báo, unused variable: `tax_rate` (biến không được dùng), vì theo góc nhìn của Rust, chẳng có gì được phép dùng nó. Cảnh báo đó biến mất cùng với lỗi.
Giống như một lá thư dán kín mà bạn viết ở bàn làm việc. Việc những tờ ghi chú nằm trên bàn lúc bạn viết chẳng có ý nghĩa gì; người mở thư chỉ thấy những gì bạn bỏ vào phong bì.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Một fn lồng được biên dịch y hệt như một fn ở đầu file. Viết nó bên trong main chỉ giấu tên của nó khỏi phần code còn lại. Nó không nối hàm đó với các biến của main, và có lý do chính đáng: một hàm bình thường không có chỗ nào để giữ chúng. Bạn có thể gọi with_tax từ bất cứ đâu mà main chạm tới được, lưu nó dưới dạng con trỏ hàm, hoặc gọi nó sau khi các biến của main đã biến mất. Một fn bình thường chỉ là code, không kèm theo dữ liệu nào.
Closure là code cộng với các biến mà nó đã bắt. Rust lưu những giá trị đó (hoặc tham chiếu tới chúng) bên trong closure, và borrow checker đảm bảo chúng sống đủ lâu. Đó là lý do rustc chỉ bạn tới closure. Xem Bắt biến từ phạm vi xung quanh.
Quy tắc tương tự áp dụng cho tham số generic: một fn bên trong cũng không dùng được T của hàm bên ngoài. Trường hợp đó là E0401.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Dùng closure
Phần tiêu đề “Cách sửa 1: Dùng closure”Phần lớn trường hợp, hãy chọn cách này, khi hàm hỗ trợ nhỏ và chỉ nằm trong một hàm. Viết let name = |params| body; và closure có thể dùng bất kỳ biến nào trong phạm vi:
fn main() { let tax_rate = 0.2;
let with_tax = |price: f64| price * (1.0 + tax_rate); // ✅ closure nhìn thấy `tax_rate`
println!("{:.2}", with_tax(10.0)); println!("{:.2}", with_tax(24.5));
let prices = [3.0, 7.5, 12.0]; let total: f64 = prices.iter().map(|&p| with_tax(p)).sum(); println!("tổng: {:.2}", total);}Kết quả:
12.0029.40tổng: 27.00Một closure chỉ đọc biến thì mượn biến đó, nên bạn vẫn dùng được tax_rate sau đó. Nếu bạn thay đổi biến bên trong closure, closure cần let mut. Xem Closure.
Cách sửa 2: Truyền giá trị vào làm đối số
Phần tiêu đề “Cách sửa 2: Truyền giá trị vào làm đối số”Chọn cách này khi hàm hỗ trợ nên tiếp tục là một hàm thật sự: nó dài hơn, bạn muốn viết test cho nó, hoặc nhiều hàm cùng dùng nó. Hãy cho nó thêm một tham số và truyền giá trị vào:
fn with_tax(price: f64, tax_rate: f64) -> f64 { price * (1.0 + tax_rate) // ✅ giờ `tax_rate` là một tham số}
fn receipt(prices: &[f64], tax_rate: f64) -> f64 { prices.iter().map(|&p| with_tax(p, tax_rate)).sum()}
fn main() { let tax_rate = 0.2; println!("{:.2}", with_tax(10.0, tax_rate)); println!("tổng: {:.2}", receipt(&[3.0, 7.5, 12.0], tax_rate));
let reduced_rate = 0.05; println!("{:.2}", with_tax(10.0, reduced_rate));}Kết quả:
12.00tổng: 27.0010.50Ở đây hàm đã được chuyển lên cấp cao nhất, nơi phần lớn hàm hỗ trợ trong Rust sinh sống. Giữ nó lồng bên trong cũng được; điều quan trọng là giá trị được đưa vào qua đối số. Thêm một điểm cộng: giờ nó dùng được với bất kỳ mức thuế nào, không chỉ một. Xem Tham số.
Cách sửa 3: Biến nó thành const
Phần tiêu đề “Cách sửa 3: Biến nó thành const”Chọn cách này khi giá trị cố định và đã biết trước khi chương trình chạy, như một mức thuế được viết thẳng trong code. Khác với biến let, một const là một mục, và hàm nào cũng dùng được nó, dù lồng hay không:
const TAX_RATE: f64 = 0.2; // ✅ một hằng số, mọi hàm đều nhìn thấy
fn main() { fn with_tax(price: f64) -> f64 { price * (1.0 + TAX_RATE) }
println!("{:.2}", with_tax(10.0)); println!("{:.2}", with_tax(24.5));}Kết quả:
12.0029.40Cách này chỉ dùng được cho giá trị tính được lúc biên dịch. Nếu mức thuế đến từ file, từ dữ liệu người dùng nhập, hoặc từ một biến khác, const sẽ không làm được: const RATE: f64 = tax_rate; gây ra một lỗi khác, E0435 (attempt to use a non-constant value in a constant). Khi đó hãy dùng cách sửa 1 hoặc cách sửa 2. Xem Hằng số.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Khi bạn đến từ JavaScript hoặc Python. Trong những ngôn ngữ đó, hàm lồng nhìn thấy biến bên ngoài, nên viết fn trong fn có vẻ tự nhiên. Trong Rust, thứ làm được điều đó là closure. Cách sửa: let helper = |x| ...; thay cho fn helper(x) ....
Dùng self trong một fn lồng bên trong phương thức. fn report(&self) { fn show() { println!("{}", self.n); } } cho ra cùng lỗi E0434, chỉ vào self. Cách sửa: biến show thành một phương thức (fn show(&self) trong impl) và gọi self.show(), hoặc dùng closure.
Một tham số hàm, không phải let. Trong fn total(prices: &[f64], discount: f64), một fn apply(price: f64) -> f64 { price - discount } lồng bên trong cũng gây ra E0434: tham số cũng là biến cục bộ. Cách sửa: truyền discount vào apply, hoặc viết let apply = |price: f64| price - discount;.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0401: can’t use generic parameters from outer item: cùng quy tắc đó cho kiểu: một
fnbên trong không dùng đượcThoặcSelfcủa bên ngoài. - E0425: cannot find value in this scope: cái tên hoàn toàn không tồn tại, ví dụ do gõ sai, hoặc một biến của hàm khác.
- E0373: closure may outlive the current function: lỗi bạn có thể gặp sau khi chuyển sang closure, nếu nó mượn một biến và sống lâu hơn biến đó (cách sửa:
move). - E0424:
selfis not available here:selfbị dùng trong một hàm không phải phương thức. - E0435: một
constcố dùng một biếnlet, như ở cách sửa 3. - 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. Bên trong main, bạn có let limit = 10; và một fn check(n: i32) -> bool { n < limit } lồng bên trong. Chuyện gì xảy ra?
2. Phiên bản nào dùng được biến cục bộ limit?
3. Giá trị đến từ dữ liệu người dùng nhập lúc chạy. Cách sửa nào sẽ KHÔNG dùng được?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0434 — Rust error codes index (tiếng Anh): phần giải thích chính thức, với cách sửa bằng closure và bằng
const/static. - Closures — The Rust Book (tiếng Anh): closure bắt môi trường xung quanh thế nào, điều mà
fnlồng không làm được. - Capturing — Rust by Example (tiếng Anh): mượn, mượn khả biến và move vào closure, với các ví dụ nhỏ.
- Item declarations — The Rust Reference (tiếng Anh): quy tắc rằng một mục bên trong hàm không dùng được tham số hay biến cục bộ của hàm đó.
- Constants — The Rust Book (tiếng Anh):
constkhácletthế nào, và một biểu thức hằng có thể chứa những gì.