Ir al contenido

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.)

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

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

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.

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.

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.00
29.40
total: 27.00
▶ Pruébalo tú mismo

Una 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.

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.00
total: 27.00
10.50
▶ Pruébalo tú mismo

Aquí 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.

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.00
29.40
▶ Pruébalo tú mismo

Esto 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.

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;.

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