跳转到内容

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));
}

^^^ 指着第 5 行的 tax_rate。它是 main 的局部变量,在第 2 行用 let 创建。内部函数 with_tax 想读取它,这是不允许的。

help: 那一行给出了修法:用闭包 || { ... } 代替嵌套的 fn。闭包能用它周围的变量,fn 则不能,不管你把它写在哪里。

你常常还会看到一条警告 unused variable: `tax_rate` (未使用的变量),因为在 Rust 看来,没有任何代码能合法地用到它。错误修好后,这条警告也会消失。

就像你在书桌前写好并封上的一封信。写信时你的笔记就摊在桌上,但这没关系;拆信的人只能看到你装进信封里的东西。

嵌套的 fn 和写在文件最外层的函数编译方式完全一样。把它写在 main 里,只是让其余代码看不到它的名字而已。这并不会把它和 main 的变量连起来,这样做是有原因的:一个普通函数没有地方存放这些变量。只要 main 能访问到 with_tax,你就能在任何地方调用它,把它存成函数指针,甚至在 main 的变量已经消失之后再调用它。普通的 fn 只是代码,不带任何数据。

闭包则是代码加上它捕获的变量。Rust 把这些值(或者指向它们的引用)存进闭包里,借用检查器会确保它们活得足够久。所以 rustc 会建议你用闭包。参见捕获外部作用域的变量。

同样的规则也适用于泛型参数:内部 fn 也不能用外层函数的 T。那是 E0401。

大多数时候选这个方法,适合只在一个函数里用到的小辅助函数。写成 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.00
29.40
合计:27.00
▶ 动手试一试

只读取变量的闭包会借用它,所以之后你还能继续用 tax_rate。如果在闭包里修改这个变量,闭包就需要写成 let mut。参见闭包。

当这个辅助函数应该保持为一个真正的函数时选这个方法:它比较长,你想测试它,或者有好几个函数要用它。给它多加一个参数,把值传进去:

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.00
10.50
▶ 动手试一试

这里把函数移到了最外层,大多数 Rust 辅助函数都放在那里。继续嵌套着写也可以;关键是值要通过参数传进来。额外的好处是:它现在能用任何税率,而不只是一个。参见参数。

当这个值是固定的、在程序运行前就已知时选这个方法,比如直接写在代码里的税率。和 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.00
29.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;。

小测验

1. 在 main 里有 let limit = 10;,还有一个嵌套的 fn check(n: i32) -> bool { n < limit }。会发生什么?

2. 哪种写法能用到局部变量 limit?

3. 这个值来自运行时的用户输入。哪种修法行不通?

得分: 0 / 3