Introduction : pourquoi l'itérateur ne veut-il pas s'arrêter ?
\n\nImaginez : vous écrivez un programme en Rust qui traite une liste de nombres — analyse des chaînes, lecture de fichiers, effectue des transformations. Vous utilisez Iterator::map pour parcourir chaque élément. Et soudain — boum ! — l'un des éléments se transforme en erreur Result::Err. Il serait logique d'arrêter immédiatement l'itération et de retourner cette erreur. Mais map continue simplement de fonctionner comme si de rien n'était. Pourquoi ? Et surtout — comment y remédier ?
Cette question est l'une des plus populaires sur Stack Overflow parmi les débutants en Rust. Et pour cause. Les mécanismes de gestion des erreurs en Rust sont flexibles, mais nécessitent une approche réfléchie. Dans cet article, nous analyserons le problème en profondeur, examinerons plusieurs solutions et choisirons la plus élégante. C'est parti !
\n\nProblème : map ne s'arrête pas
\n\nCommençons par un exemple classique. Supposons que nous ayons un vecteur de chaînes que nous voulons convertir en nombres :
\n\nfn main() {\n let inputs = vec!["42", "hello", "100"];\n\n let result: Vec<Result<i32, std::num::ParseIntError>> =\n inputs.iter().map(|s| s.parse::<i32>()).collect();\n\n println!("{:?}", result);\n}\n\nCe code se compile et s'exécute. Mais qu'obtenons-nous ? Un vecteur de Result : [Ok(42), Err(ParseIntError), Ok(100)]. L'itération ne s'est pas arrêtée sur "hello", mais a continué. Pour de nombreuses tâches, c'est normal — vous voulez collecter toutes les erreurs. Mais que faire si vous avez besoin de la première exception ? Par exemple, lors du chargement d'une configuration : si un paramètre est incorrect, il est inutile de vérifier les suivants.
Le problème est que map est une transformation paresseuse. Il ne sait pas ce qu'est un "arrêt". Il dit simplement : "Donne-moi un élément, j'applique la fonction, je retourne le résultat". Tout le contrôle de la pause et de la reprise repose sur le consommateur de l'itérateur (collect, for, fold, etc.). Et collect pour Vec va simplement jusqu'à la fin.
Solution 1 : collect avec FromIterator (la plus populaire)
\n\nLa réponse la plus courante sur Stack Overflow est d'utiliser collect::<Result<Vec<_>, _>>(). Rust sait qu'il peut collecter un itérateur de Result en un seul Result contenant un vecteur. Si un Err est rencontré, la collecte s'arrête et cette erreur est retournée.
fn main() -> Result<(), std::num::ParseIntError> {\n let inputs = vec!["42", "hello", "100"];\n\n let result: Vec<i32> =\n inputs.iter()\n .map(|s| s.parse::<i32>())\n .collect::<Result<Vec<_>, _>>()?; // <-- magie !\n\n println!("{:?}", result);\n Ok(())\n}\n\nComment cela fonctionne-t-il ? Le type Result<Vec<i32>, ParseIntError> implémente le trait FromIterator. Lorsque collect voit un Err, il le retourne immédiatement, sans traiter les éléments restants. Si tout va bien, vous obtenez Ok(vecteur). C'est court, idiomatique et efficace.
Inconvénient : vous perdez l'information sur l'élément qui a provoqué l'erreur (sauf si vous l'enveloppez dans votre propre type). Mais pour des scripts rapides, c'est parfait.
\n\nSolution 2 : filter_map pour ignorer les erreurs
\n\nParfois, vous ne voulez pas vous arrêter, mais simplement ignorer les éléments en échec. Par exemple, ne compter que les chaînes valides. Pour cela, filter_map est idéal :
fn main() {\n let inputs = vec!["42", "hello", "100", "abc", "7"];\n\n let numbers: Vec<i32> = inputs.iter()\n .filter_map(|s| s.parse::<i32>().ok()) // .ok() transforme Err en None\n .collect();\n\n println!("{:?}", numbers); // [42, 100, 7]\n}\n\nfilter_map prend une