Aller au contenu

Erreur Rust E0434 : can't capture dynamic environment in a fn item (fn imbriquée)

E0434 veut dire : une fonction écrite à l’intérieur d’une autre fonction a essayé d’utiliser une des variables locales de la fonction extérieure. On dirait que ça devrait marcher : la variable est juste là, quelques lignes plus haut. Mais en Rust, une fn imbriquée est un élément (item) à part, et elle ne voit pas les variables qui l’entourent. La formulation du compilateur, « dynamic environment » (environnement dynamique), veut simplement dire « les variables locales qui existent pendant que la fonction extérieure s’exécute ». C’est l’une des erreurs les plus faciles à corriger : rustc vous donne la réponse dans sa ligne help:. Cette page fait partie du guide des erreurs du compilateur Rust.

error[E0434]: can't capture dynamic environment in a fn item
--> src/main.rs:5:24
|
5 | price * (1.0 + tax_rate) // ❌ la `fn` imbriquée ne voit pas `tax_rate`
| ^^^^^^^^
|
= help: use the `|| { ... }` closure form instead

Le compilateur vous dit : « impossible de capturer l’environnement dynamique dans un élément fn », et l’aide propose d’utiliser plutôt la forme closure, || { ... }.

Voici le programme qui la provoque :

fn main() {
let tax_rate = 0.2;
fn with_tax(price: f64) -> f64 {
price * (1.0 + tax_rate) // ❌ la `fn` imbriquée ne voit pas `tax_rate`
}
println!("{:.2}", with_tax(10.0));
}

Les ^^^ pointent tax_rate à la ligne 5. C’est une variable locale de main, créée avec let à la ligne 2. La fonction intérieure with_tax essaie de la lire, et ce n’est pas permis.

La ligne help: donne la correction : écrivez une closure (fonction anonyme), || { ... }, au lieu d’une fn imbriquée. Une closure peut utiliser les variables qui l’entourent. Une fn ne le peut pas, où que vous l’écriviez.

Vous aurez souvent aussi un avertissement, unused variable: `tax_rate` (variable inutilisée), parce que du point de vue de Rust, rien ne pouvait légalement s’en servir. Cet avertissement disparaît avec l’erreur.

C’est comme une lettre cachetée que vous avez écrite à votre bureau. Peu importe que vos notes aient été sur le bureau pendant que vous l’écriviez : la personne qui l’ouvre ne voit que ce que vous avez mis dans l’enveloppe.

Une fn imbriquée est compilée exactement comme une fonction écrite en haut du fichier. L’écrire à l’intérieur de main cache seulement son nom au reste du code. Ça ne la relie pas aux variables de main, et il y a une bonne raison à ça : une simple fonction n’a nulle part où les garder. Vous pouvez appeler with_tax depuis n’importe quel endroit où main peut l’atteindre, la stocker comme pointeur de fonction, ou l’appeler quand les variables de main n’existent plus. Une fn ordinaire, c’est juste du code, sans données attachées.

Une closure, c’est du code plus les variables qu’elle a capturées. Rust range ces valeurs (ou des références vers elles) à l’intérieur de la closure, et le borrow checker vérifie qu’elles vivent assez longtemps. Voilà pourquoi rustc vous oriente vers les closures. Voir Capturer des variables de la portée environnante.

La même règle vaut pour les paramètres génériques : une fn imbriquée ne peut pas non plus utiliser le T de la fonction extérieure. Ça, c’est E0401.

Choisissez-la la plupart du temps, quand la fonction d’aide est petite et ne sert que dans une fonction. Écrivez let nom = |paramètres| corps; et la closure peut utiliser n’importe quelle variable de la portée :

fn main() {
let tax_rate = 0.2;
let with_tax = |price: f64| price * (1.0 + tax_rate); // ✅ une closure voit `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);
}

Sortie :

12.00
29.40
total : 27.00
▶ Essayez par vous-même

Une closure qui se contente de lire une variable l’emprunte, donc vous pouvez encore utiliser tax_rate ensuite. Si vous modifiez la variable dans la closure, la closure a besoin de let mut. Voir Les closures.

Choisissez-la quand la fonction d’aide doit rester une vraie fonction : elle est plus longue, vous voulez la tester, ou plusieurs fonctions s’en servent. Ajoutez-lui un paramètre et passez-lui la valeur :

fn with_tax(price: f64, tax_rate: f64) -> f64 {
price * (1.0 + tax_rate) // ✅ `tax_rate` est maintenant un paramètre
}
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));
}

Sortie :

12.00
total : 27.00
10.50
▶ Essayez par vous-même

Ici, la fonction est passée au niveau supérieur du fichier, là où vivent la plupart des fonctions d’aide en Rust. Ça marche aussi si vous la gardez imbriquée : ce qui compte, c’est que la valeur arrive en argument. En prime, elle marche maintenant avec n’importe quel taux, pas un seul. Voir Les paramètres.

Choisissez-la quand la valeur est fixe et connue avant que le programme démarre, comme un taux de TVA écrit dans le code. Contrairement à une variable let, une const est un élément (item), et n’importe quelle fonction peut l’utiliser, imbriquée ou non :

const TAX_RATE: f64 = 0.2; // ✅ une constante, visible par toutes les fonctions
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));
}

Sortie :

12.00
29.40
▶ Essayez par vous-même

Ça ne marche que pour des valeurs calculables à la compilation. Si le taux vient d’un fichier, d’une saisie de l’utilisateur ou d’une autre variable, une const ne convient pas : const RATE: f64 = tax_rate; donne une autre erreur, E0435 (attempt to use a non-constant value in a constant, « tentative d’utiliser une valeur non constante dans une constante »). Utilisez alors la solution 1 ou la solution 2. Voir Les constantes.

Quand vous venez de JavaScript ou de Python. Dans ces langages, une fonction imbriquée voit les variables extérieures, donc une fn dans une fn paraît naturelle. En Rust, ce qui fait ça, c’est une closure. Correction : let helper = |x| ...; au lieu de fn helper(x) ....

Utiliser self dans une fn imbriquée à l’intérieur d’une méthode. fn report(&self) { fn show() { println!("{}", self.n); } } donne la même E0434, qui pointe self. Correction : faites de show une méthode (fn show(&self) dans le bloc impl) et appelez self.show(), ou utilisez une closure.

Un paramètre de fonction, pas un let. Dans fn total(prices: &[f64], discount: f64), une fn apply(price: f64) -> f64 { price - discount } imbriquée donne aussi E0434 : les paramètres sont eux aussi des variables locales. Correction : passez discount à apply, ou écrivez let apply = |price: f64| price - discount;.

Petit quiz

1. Dans main, vous avez let limit = 10; et une fn check(n: i32) -> bool { n < limit } imbriquée. Que se passe-t-il ?

2. Quelle version peut utiliser la variable locale limit ?

3. La valeur vient d'une saisie de l'utilisateur pendant l'exécution. Quelle correction ne marchera PAS ?

Score : 0 / 3