Rust 错误 E0434:can't capture dynamic environment in a fn item(内部 fn 不能捕获局部变量)
E0434 的意思是:写在另一个函数里面的函数,想用外层函数的某个局部变量。 看起来这应该行得通:变量就在上面几行。但在 Rust 里,嵌套的 fn 是一个独立的项(item),看不到它周围的变量。编译器说的“dynamic environment”(动态环境),其实就是“外层函数运行时存在的那些局部变量”。这是最好修的错误之一:rustc 在 help: 那一行就把答案告诉你了。(本页属于 Rust 编译错误详解系列。)
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 instead第一行的意思是“fn 项里不能捕获动态环境”。help 建议改用 || { ... } 这种闭包写法。
产生这个错误的程序是这样的:
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));}这个错误是什么意思
Section titled “这个错误是什么意思”^^^ 指着第 5 行的 tax_rate。它是 main 的局部变量,在第 2 行用 let 创建。内部函数 with_tax 想读取它,这是不允许的。
help: 那一行给出了修法:用闭包 || { ... } 代替嵌套的 fn。闭包能用它周围的变量,fn 则不能,不管你把它写在哪里。
你常常还会看到一条警告 unused variable: `tax_rate` (未使用的变量),因为在 Rust 看来,没有任何代码能合法地用到它。错误修好后,这条警告也会消失。
就像你在书桌前写好并封上的一封信。写信时你的笔记就摊在桌上,但这没关系;拆信的人只能看到你装进信封里的东西。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”嵌套的 fn 和写在文件最外层的函数编译方式完全一样。把它写在 main 里,只是让其余代码看不到它的名字而已。这并不会把它和 main 的变量连起来,这样做是有原因的:一个普通函数没有地方存放这些变量。只要 main 能访问到 with_tax,你就能在任何地方调用它,把它存成函数指针,甚至在 main 的变量已经消失之后再调用它。普通的 fn 只是代码,不带任何数据。
闭包则是代码加上它捕获的变量。Rust 把这些值(或者指向它们的引用)存进闭包里,借用检查器会确保它们活得足够久。所以 rustc 会建议你用闭包。参见捕获外部作用域的变量。
同样的规则也适用于泛型参数:内部 fn 也不能用外层函数的 T。那是 E0401。
修复 1:改用闭包
Section titled “修复 1:改用闭包”大多数时候选这个方法,适合只在一个函数里用到的小辅助函数。写成 let 名字 = |参数| 函数体;,闭包就能用作用域里的任何变量:
fn main() { let tax_rate = 0.2;
let with_tax = |price: f64| price * (1.0 + tax_rate); // ✅ 闭包能看到 `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!("合计:{:.2}", total);}输出:
12.0029.40合计:27.00只读取变量的闭包会借用它,所以之后你还能继续用 tax_rate。如果在闭包里修改这个变量,闭包就需要写成 let mut。参见闭包。
修复 2:把值作为参数传进去
Section titled “修复 2:把值作为参数传进去”当这个辅助函数应该保持为一个真正的函数时选这个方法:它比较长,你想测试它,或者有好几个函数要用它。给它多加一个参数,把值传进去:
fn with_tax(price: f64, tax_rate: f64) -> f64 { price * (1.0 + tax_rate) // ✅ `tax_rate` 现在是参数了}
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!("合计:{:.2}", receipt(&[3.0, 7.5, 12.0], tax_rate));
let reduced_rate = 0.05; println!("{:.2}", with_tax(10.0, reduced_rate));}输出:
12.00合计:27.0010.50这里把函数移到了最外层,大多数 Rust 辅助函数都放在那里。继续嵌套着写也可以;关键是值要通过参数传进来。额外的好处是:它现在能用任何税率,而不只是一个。参见参数。
修复 3:改成 const
Section titled “修复 3:改成 const”当这个值是固定的、在程序运行前就已知时选这个方法,比如直接写在代码里的税率。和 let 变量不同,const 是一个项,任何函数都能用它,嵌套的也不例外:
const TAX_RATE: f64 = 0.2; // ✅ 常量,每个函数都能看到
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));}输出:
12.0029.40这只适用于能在编译时算出来的值。如果税率来自文件、用户输入或者另一个变量,const 就不行了:const RATE: f64 = tax_rate; 会报另一个错误 E0435(attempt to use a non-constant value in a constant,在常量里使用了非常量的值)。这时请用修复 1 或修复 2。参见常量。
从 JavaScript 或 Python 转过来。 在这些语言里,嵌套函数能看到外层的变量,所以在 fn 里写 fn 感觉很自然。在 Rust 里,能做到这一点的是闭包。修复:用 let helper = |x| ...; 代替 fn helper(x) ...。
在方法里的嵌套 fn 中使用 self。 fn report(&self) { fn show() { println!("{}", self.n); } } 也会报 E0434,指向 self。修复:把 show 改成方法(在 impl 里写 fn show(&self)),然后调用 self.show(),或者改用闭包。
是函数参数,而不是 let 变量。 在 fn total(prices: &[f64], discount: f64) 里,嵌套的 fn apply(price: f64) -> f64 { price - discount } 同样会报 E0434:参数也是局部变量。修复:把 discount 传给 apply,或者写成 let apply = |price: f64| price - discount;。
- E0401:can’t use generic parameters from outer item:同样的规则用在类型上:内部
fn不能用外层的T或Self。 - E0425:cannot find value in this scope:这个名字根本不存在,比如拼错了,或者是另一个函数里的变量。
- E0373:closure may outlive the current function:改用闭包之后可能碰到的错误,闭包借用了一个变量,却活得比它更久(修复:加
move)。 - E0424:
selfis not available here:在不是方法的函数里用了self。 - E0435:
const想用一个let变量,见修复 3。 - 所有编译错误详解
小测验
1. 在 main 里有 let limit = 10;,还有一个嵌套的 fn check(n: i32) -> bool { n < limit }。会发生什么?
2. 哪种写法能用到局部变量 limit?
3. 这个值来自运行时的用户输入。哪种修法行不通?
得分: 0 / 3
- E0434 — Rust error codes index(英文):官方解释,附有闭包修法和
const/static修法。 - 闭包 — Rust 程序设计语言:闭包怎样捕获它的环境,这是嵌套的
fn做不到的。 - 捕获 — 通过例子学 Rust:借用、可变借用和移动进闭包,配有简短的示例。
- Item declarations — The Rust Reference(英文):函数里的项不能使用该函数的参数或局部变量的规则。
- 变量和可变性 — Rust 程序设计语言:
const和let有什么不同,以及常量表达式里能写什么。