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.
Le message d’erreur
Section intitulée « Le message d’erreur »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 insteadLe 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));}Ce que ça veut dire
Section intitulée « Ce que ça veut dire »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.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »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.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : utiliser une closure
Section intitulée « Solution 1 : utiliser une closure »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.0029.40total : 27.00Une 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.
Solution 2 : passer la valeur en argument
Section intitulée « Solution 2 : passer la valeur en argument »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.00total : 27.0010.50Ici, 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.
Solution 3 : en faire une const
Section intitulée « Solution 3 : en faire une const »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.0029.40Ç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.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »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;.
Erreurs liées
Section intitulée « Erreurs liées »- E0401 : can’t use generic parameters from outer item : la même règle pour les types : une
fnimbriquée ne peut pas utiliser leTou leSelfextérieur. - E0425 : cannot find value in this scope : le nom n’existe pas du tout, par exemple à cause d’une faute de frappe, ou d’une variable d’une autre fonction.
- E0373 : closure may outlive the current function : ce que vous pouvez rencontrer après être passé à une closure, si elle emprunte une variable et lui survit (correction :
move). - E0424 :
selfis not available here :selfutilisé dans une fonction qui n’est pas une méthode. - E0435 : une
constqui essaie d’utiliser une variablelet, comme dans la solution 3. - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »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
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0434 — Rust error codes index (en anglais) : l’explication officielle, avec la correction par closure et celle par
const/static. - Closures — The Rust Book (en anglais) : comment les closures capturent leur environnement, ce qu’une
fnimbriquée ne sait pas faire. - Capturing — Rust by Example (en anglais) : emprunt, emprunt mutable et déplacement dans une closure, avec de petits exemples.
- Item declarations — The Rust Reference (en anglais) : la règle qui interdit à un élément déclaré dans une fonction d’utiliser les paramètres ou les variables locales de cette fonction.
- Constants — The Rust Book (en anglais) : ce qui distingue
constdelet, et ce qu’une expression constante peut contenir.