Error E0434 de Rust: can't capture dynamic environment in a fn item
E0434 significa: una función escrita dentro de otra función intentó usar una de las variables locales de la función de fuera. Parece que debería funcionar: la variable está ahí mismo, unas líneas más arriba. Pero en Rust una fn anidada es un elemento (item) aparte, y no puede ver las variables que la rodean. Las palabras del compilador, “dynamic environment” (entorno dinámico), solo quieren decir “las variables locales que existen mientras se ejecuta la función de fuera”. Es uno de los errores más fáciles de arreglar: rustc te da la respuesta en su línea help:. (Esta página forma parte de Errores del compilador de Rust explicados.)
El mensaje de error
Sección titulada «El mensaje de error»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 insteadEn español: “no se puede capturar el entorno dinámico en un elemento fn”. El help te sugiere usar en su lugar la forma de closure, || { ... }.
Este es el programa que lo produce:
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));}Qué significa
Sección titulada «Qué significa»El ^^^ señala tax_rate en la línea 5. Es una variable local de main, creada con let en la línea 2. La función interna with_tax intenta leerla, y eso no está permitido.
La línea help: te da la solución: escribe una closure (función anónima), || { ... }, en lugar de una fn anidada. Una closure puede usar las variables que la rodean. Una fn no puede, la escribas donde la escribas.
A menudo también verás una advertencia, unused variable: `tax_rate` (variable sin usar), porque desde el punto de vista de Rust nada podía usarla legalmente. Esa advertencia desaparece junto con el error.
Es como una carta cerrada que escribiste en tu escritorio. Da igual que tus apuntes estuvieran sobre la mesa mientras la escribías: quien abra el sobre solo ve lo que metiste dentro.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»Una fn anidada se compila exactamente igual que una escrita al principio del archivo. Escribirla dentro de main solo esconde su nombre del resto del código. No la conecta con las variables de main, y hay una buena razón: una función normal no tiene dónde guardarlas. Puedes llamar a with_tax desde cualquier lugar donde main llegue a ella, guardarla como puntero a función o llamarla cuando las variables de main ya no existen. Una fn normal es solo código, sin datos asociados.
Una closure es código más las variables que capturó. Rust guarda esos valores (o referencias a ellos) dentro de la closure, y el borrow checker se asegura de que vivan lo suficiente. Por eso rustc te manda a las closures. Mira Capturar variables del ámbito que la rodea.
La misma regla vale para los parámetros genéricos: una fn interna tampoco puede usar el T de la función de fuera. Ese es E0401.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Usa una closure
Sección titulada «Solución 1: Usa una closure»Elige esta casi siempre, cuando la función auxiliar es pequeña y vive dentro de una sola función. Escribe let nombre = |parámetros| cuerpo; y la closure podrá usar cualquier variable del ámbito:
fn main() { let tax_rate = 0.2;
let with_tax = |price: f64| price * (1.0 + tax_rate); // ✅ una closure sí ve `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);}Salida:
12.0029.40total: 27.00Una closure que solo lee una variable la toma prestada, así que puedes seguir usando tax_rate después. Si cambias la variable dentro de la closure, la closure necesita let mut. Mira Closures.
Solución 2: Pasa el valor como argumento
Sección titulada «Solución 2: Pasa el valor como argumento»Elige esta cuando la función auxiliar debe seguir siendo una función de verdad: es más larga, quieres probarla con tests o la usan varias funciones. Dale un parámetro más y pásale el valor:
fn with_tax(price: f64, tax_rate: f64) -> f64 { price * (1.0 + tax_rate) // ✅ ahora `tax_rate` es un parámetro}
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));}Salida:
12.00total: 27.0010.50Aquí la función pasó al nivel superior del archivo, que es donde viven la mayoría de las funciones auxiliares en Rust. También funciona si la dejas anidada; lo que importa es que el valor entre como argumento. De regalo: ahora sirve para cualquier tasa, no solo para una. Mira Parámetros.
Solución 3: Conviértelo en un const
Sección titulada «Solución 3: Conviértelo en un const»Elige esta cuando el valor es fijo y se conoce antes de que el programa se ejecute, como una tasa de impuestos escrita en el código. A diferencia de una variable let, un const es un elemento (item), y cualquier función puede usarlo, esté anidada o no:
const TAX_RATE: f64 = 0.2; // ✅ una constante, visible para todas las funciones
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));}Salida:
12.0029.40Esto solo funciona con valores que se pueden calcular al compilar. Si la tasa viene de un archivo, de lo que escribe el usuario o de otra variable, un const no sirve: const RATE: f64 = tax_rate; da otro error, E0435 (attempt to use a non-constant value in a constant, intento de usar un valor no constante en una constante). Entonces usa la Solución 1 o la 2. Mira Constantes.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»Si vienes de JavaScript o Python. En esos lenguajes una función anidada ve las variables de fuera, así que poner una fn dentro de otra fn parece natural. En Rust, lo que hace eso es una closure. Solución: let helper = |x| ...; en lugar de fn helper(x) ....
Usar self en una fn anidada dentro de un método. fn report(&self) { fn show() { println!("{}", self.n); } } da el mismo E0434, señalando self. Solución: convierte show en un método (fn show(&self) en el impl) y llama a self.show(), o usa una closure.
Un parámetro de función, no un let. En fn total(prices: &[f64], discount: f64), una fn apply(price: f64) -> f64 { price - discount } anidada también da E0434: los parámetros también son variables locales. Solución: pásale discount a apply, o escribe let apply = |price: f64| price - discount;.
Errores relacionados
Sección titulada «Errores relacionados»- E0401: can’t use generic parameters from outer item: la misma regla para los tipos: una
fninterna no puede usar elTni elSelfde fuera. - E0425: cannot find value in this scope: el nombre no existe en absoluto, por ejemplo por una errata o porque la variable es de otra función.
- E0373: closure may outlive the current function: lo que te puede pasar después de cambiar a una closure, si toma prestada una variable y vive más que ella (solución:
move). - E0424:
selfis not available here:selfusado en una función que no es un método. - E0435: un
constque intenta usar una variablelet, como se ve en la Solución 3. - Todos los errores del compilador explicados
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. Dentro de main tienes let limit = 10; y una fn check(n: i32) -> bool { n < limit } anidada. ¿Qué pasa?
2. ¿Qué versión puede usar la variable local limit?
3. El valor viene de lo que escribe el usuario mientras el programa se ejecuta. ¿Qué solución NO funcionará?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0434 — Rust error codes index (en inglés): la explicación oficial, con la solución de la closure y la de
const/static. - Closures — El libro de Rust (en inglés): cómo capturan las closures su entorno, algo que una
fnanidada no puede hacer. También existe una traducción al español de la comunidad del libro. - Capturing — Rust by Example (en inglés): préstamo, préstamo mutable y mover valores a una closure, con ejemplos pequeños.
- Item declarations — The Rust Reference (en inglés): la regla de que un elemento dentro de una función no puede usar los parámetros ni las variables locales de esa función.
- Constants — El libro de Rust (en inglés): en qué se diferencia
constdelet, y qué puede contener una expresión constante.