Перейти к содержимому

Ошибка Rust E0434: can't capture dynamic environment in a fn item (переменная снаружи)

E0434 значит: функция, написанная внутри другой функции, попыталась использовать одну из локальных переменных внешней. Кажется, что это должно работать: переменная прямо здесь, на несколько строк выше. Но вложенная fn в Rust — отдельный элемент, и переменные вокруг себя она не видит. Слова компилятора «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 советует использовать вместо этого замыкание вида || { ... }.

Вот программа, которая её вызывает (комментарий // ❌ оставлен на английском, потому что rustc цитирует эту строку; он значит «внутренняя fn не видит tax_rate»):

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

^^^ указывает на tax_rate в строке 5. Это локальная переменная main, созданная через let в строке 2. Внутренняя функция with_tax пытается её прочитать, а так нельзя.

Строка help: даёт исправление: напиши замыкание, || { ... }, вместо вложенной fn. Замыкание может использовать переменные вокруг себя. А fn не может, где бы ты её ни написал.

Часто вместе с ошибкой приходит ещё и предупреждение unused variable: `tax_rate` («неиспользуемая переменная»), потому что с точки зрения Rust ничто не может законно её использовать. Это предупреждение исчезнет вместе с ошибкой.

Это как запечатанное письмо, которое ты написал за своим столом. Неважно, что на столе лежали твои заметки, пока ты писал: тот, кто вскроет конверт, увидит только то, что ты положил внутрь.

Вложенная fn компилируется точно так же, как функция в начале файла. Если написать её внутри main, её имя просто станет скрыто от остального кода. С переменными main она от этого не связывается, и на то есть веская причина: у обычной функции нет места, где их хранить. with_tax можно вызвать откуда угодно, откуда её видно из main, сохранить как указатель на функцию или вызвать уже после того, как переменных main не стало. Обычная fn — это просто код, без прикреплённых данных.

Замыкание — это код плюс захваченные переменные. Rust хранит эти значения (или ссылки на них) внутри замыкания, а анализатор заимствований следит, чтобы они жили достаточно долго. Поэтому rustc и отправляет тебя к замыканиям. Подробнее — в уроке Замыкания.

То же правило действует для обобщённых параметров: внутренняя fn не может использовать и T внешней функции. Это уже E0401.

Это нужно чаще всего — когда вспомогательный код маленький и живёт внутри одной функции. Напиши let name = |params| body;, и замыкание сможет использовать любую переменную в области видимости:

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. Подробнее — в уроке Замыкания.

Исправление 2: Передай значение аргументом

Заголовок раздела «Исправление 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.00
10.50
▶ Попробуй сам

Здесь функция переехала на верхний уровень — там живёт большинство вспомогательных функций в Rust. Если оставить её вложенной, тоже сработает: главное, чтобы значение приходило аргументом. Бонус: теперь она работает с любой ставкой, а не только с одной. Подробнее — в уроке Функции в 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) ....

self во вложенной fn внутри метода. fn report(&self) { fn show() { println!("{}", self.n); } } даёт ту же E0434, указывая на self. Исправление: сделай show методом (fn show(&self) в impl) и вызывай 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