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.)
The error message
Section titled “The error message”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 insteadThis 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));}What it means
Section titled “What it means”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.
Why Rust does this
Section titled “Why Rust does this”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.
How to fix it
Section titled “How to fix it”Fix 1: Use a closure
Section titled “Fix 1: Use a closure”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.0029.40total: 27.00A 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.
Fix 2: Pass the value as an argument
Section titled “Fix 2: Pass the value as an argument”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.00total: 27.0010.50Here 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.
Fix 3: Make it a const
Section titled “Fix 3: Make it a const”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.0029.40This 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.
Where you’ll usually see it
Section titled “Where you’ll usually see it”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;.
Related errors
Section titled “Related errors”- E0401: can’t use generic parameters from outer item: the same rule for types: an inner
fncan’t use the outerTorSelf. - E0425: cannot find value in this scope: the name doesn’t exist at all, for example a typo, or a variable from a different function.
- E0373: closure may outlive the current function: what you can hit after switching to a closure, if it borrows a variable and outlives it (fix:
move). - E0424:
selfis not available here:selfused in a function that isn’t a method. - E0435: a
constthat tries to use aletvariable, shown in Fix 3. - All compiler errors explained
Quick check
Section titled “Quick check”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
Read more
Section titled “Read more”- E0434 — Rust error codes index: the official explanation, with the closure fix and the
const/staticfix. - Closures — The Rust Book: how closures capture their environment, which a nested
fncan’t do. - Capturing — Rust by Example: borrowing, mutable borrowing and moving into a closure, with small examples.
- Item declarations — The Rust Reference: the rule that an item inside a function can’t use the function’s parameters or local variables.
- Constants — The Rust Book: how
constdiffers fromlet, and what a constant expression can contain.