Skip to content

Rust Error E0434: Can't Capture Dynamic Environment in a fn Item

E0434 means a function written inside another function tried to use one of the outer function’s local variables. It looks like it should work: the variable is right there, a few lines up. But a nested fn in Rust is a separate item, and it can’t see the variables around it. The compiler’s wording, “dynamic environment”, just means “the local variables that exist while the outer function runs”. This is one of the friendliest errors to fix: rustc tells you the answer in its help: line. (This page is part of Rust compiler errors explained.)

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

This is the program that produces it:

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

The ^^^ points at tax_rate on line 5. That’s a local variable of main, created with let on line 2. The inner function with_tax tries to read it, and that’s not allowed.

The help: line gives the fix: write a closure, || { ... }, instead of a nested fn. A closure can use the variables around it. A fn can’t, no matter where you write it.

You’ll often also get a warning, unused variable: `tax_rate` , because from Rust’s point of view nothing could legally use it. That warning goes away with the error.

It’s like a sealed letter you wrote at your desk. It doesn’t matter that your notes were on the desk while you wrote it; the person who opens it only sees what you put inside the envelope.

A nested fn is compiled exactly like one at the top of the file. Writing it inside main only hides its name from the rest of the code. It doesn’t connect it to main’s variables, and there’s a good reason: a plain function has no place to keep them. You can call with_tax from anywhere main can reach it, store it as a function pointer, or call it after main’s variables are gone. A plain fn is just code, with no data attached.

A closure is code plus the variables it captured. Rust stores those values (or references to them) inside the closure, and the borrow checker makes sure they live long enough. That’s why rustc points you to closures. See Capturing variables from the surrounding scope.

The same rule covers generic parameters: an inner fn can’t use the outer function’s T either. That one is E0401.

Choose this most of the time, when the helper is small and lives inside one function. Write let name = |params| body; and the closure can use any variable in scope:

fn main() {
let tax_rate = 0.2;
let with_tax = |price: f64| price * (1.0 + tax_rate); // ✅ a closure sees `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!("total: {:.2}", total);
}

Output:

12.00
29.40
total: 27.00
▶ Try it Yourself

A closure that only reads a variable borrows it, so you can still use tax_rate afterwards. If you change the variable inside the closure, the closure needs let mut. See Closures.

Choose this when the helper should stay a real function: it’s longer, you want to test it, or several functions use it. Give it one more parameter and pass the value in:

fn with_tax(price: f64, tax_rate: f64) -> f64 {
price * (1.0 + tax_rate) // ✅ `tax_rate` is now a parameter
}
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!("total: {:.2}", receipt(&[3.0, 7.5, 12.0], tax_rate));
let reduced_rate = 0.05;
println!("{:.2}", with_tax(10.0, reduced_rate));
}

Output:

12.00
total: 27.00
10.50
▶ Try it Yourself

Here the function moved to the top level, which is where most Rust helpers live. It also works if you keep it nested; what matters is that the value comes in as an argument. Bonus: it now works with any rate, not just one. See Parameters.

Choose this when the value is fixed and known before the program runs, like a tax rate written in the code. Unlike a let variable, a const is an item, and any function can use it, nested or not:

const TAX_RATE: f64 = 0.2; // ✅ a constant, visible to every function
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));
}

Output:

12.00
29.40
▶ Try it Yourself

This only works for values that can be computed at compile time. If the rate comes from a file, user input, or another variable, a const won’t do: const RATE: f64 = tax_rate; gives a different error, E0435 (attempt to use a non-constant value in a constant). Then use Fix 1 or Fix 2. See Constants.

Coming from JavaScript or Python. In those languages a nested function sees the outer variables, so fn inside fn feels natural. In Rust, the thing that does that is a closure. Fix: let helper = |x| ...; instead of fn helper(x) ....

Using self in a nested fn inside a method. fn report(&self) { fn show() { println!("{}", self.n); } } gives the same E0434, pointing at self. Fix: make show a method (fn show(&self) in the impl) and call self.show(), or use a closure.

A function parameter, not a let. In fn total(prices: &[f64], discount: f64), a nested fn apply(price: f64) -> f64 { price - discount } gives E0434 too: parameters are local variables as well. Fix: pass discount to apply, or write let apply = |price: f64| price - discount;.

Quick check

1. Inside main, you have let limit = 10; and a nested fn check(n: i32) -> bool { n < limit }. What happens?

2. Which version can use the local variable limit?

3. The value comes from user input at runtime. Which fix will NOT work?

Score: 0 / 3