Ошибка 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 ничто не может законно её использовать. Это предупреждение исчезнет вместе с ошибкой.
Это как запечатанное письмо, которое ты написал за своим столом. Неважно, что на столе лежали твои заметки, пока ты писал: тот, кто вскроет конверт, увидит только то, что ты положил внутрь.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Вложенная fn компилируется точно так же, как функция в начале файла. Если написать её внутри main, её имя просто станет скрыто от остального кода. С переменными main она от этого не связывается, и на то есть веская причина: у обычной функции нет места, где их хранить. with_tax можно вызвать откуда угодно, откуда её видно из main, сохранить как указатель на функцию или вызвать уже после того, как переменных main не стало. Обычная fn — это просто код, без прикреплённых данных.
Замыкание — это код плюс захваченные переменные. Rust хранит эти значения (или ссылки на них) внутри замыкания, а анализатор заимствований следит, чтобы они жили достаточно долго. Поэтому rustc и отправляет тебя к замыканиям. Подробнее — в уроке Замыкания.
То же правило действует для обобщённых параметров: внутренняя fn не может использовать и T внешней функции. Это уже E0401.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: Используй замыкание
Заголовок раздела «Исправление 1: Используй замыкание»Это нужно чаще всего — когда вспомогательный код маленький и живёт внутри одной функции. Напиши 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.0029.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.0010.50Здесь функция переехала на верхний уровень — там живёт большинство вспомогательных функций в Rust. Если оставить её вложенной, тоже сработает: главное, чтобы значение приходило аргументом. Бонус: теперь она работает с любой ставкой, а не только с одной. Подробнее — в уроке Функции в Rust.
Исправление 3: Сделай значение const
Заголовок раздела «Исправление 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) ....
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;.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- 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и что может содержать константное выражение.