Introduction : Pourquoi Rust est-il si strict, et comment la revue de code aide à ne pas perdre la tête
\nVous avez écrit du code en Rust. Il s'est compilé — hourra ! Mais voici le problème : le développeur senior, en ouvrant votre Pull Request, soupire et commence à écrire des commentaires. Cela vous dit quelque chose ? Rust est un langage avec une philosophie unique : il vous fait confiance, mais vérifie tout. Cependant, le compilateur ne voit pas ce que voit un œil expérimenté. Il ne dira pas : « Hé, ça peut être fait plus élégamment » ou « Ton code fuit — pas de la mémoire, mais des performances ».
\nDans cet article, nous allons effectuer une revue de code Rust et analyser les remarques typiques des seniors. Vous apprendrez à transformer un « code qui fonctionne juste » en un code idiomatique et fiable. C'est parti !
\n\n1. Ignorer le système de types : « Et si je mettais tout dans un String »
\nL'erreur la plus fréquente des débutants est d'utiliser String ou des types génériques là où des types spécifiques sont nécessaires. Le senior dira : « Ton type doit refléter la logique métier, pas juste être une chaîne de caractères ».
Problème : perte de sémantique
\nImaginez que vous écrivez une fonction pour travailler avec des adresses email :
\nfn send_email(to: String, subject: String, body: String) {\n // ...\n}\nQu'est-ce qui ne va pas ici ? Vous pouvez confondre to et subject — le compilateur ne le remarquera pas. Le senior proposera d'utiliser le newtype-pattern :
struct Email(String);\nstruct Subject(String);\nstruct Body(String);\n\nfn send_email(to: Email, subject: Subject, body: Body) {\n // ...\n}\nCela semble excessif ? Lors d'une code review Rust, cette approche est la référence. Elle rend le code auto-documenté et élimine toute une classe d'erreurs.
\n\n2. Abus de clone() : copier ne signifie pas posséder
\nLes développeurs juniors utilisent souvent .clone() partout où le compilateur se plaint des emprunts. Le senior dira : « Tu abandonnes face au borrow checker. Pense différemment ».
Exemple : clone inutile
\nfn process(data: Vec<i32>) {\n let first = data[0].clone();\n let second = data[1].clone();\n // ...\n}\ni32 implémente le trait Copy — clone() est ici totalement superflu. Mais le problème est plus profond : si vous clonez de grandes structures (comme Vec ou String), vous subissez un impact sur les performances.
La bonne approche est de travailler avec des références :
\nfn process(data: &[i32]) {\n let first = &data[0];\n let second = &data[1];\n // ...\n}\nLors d'une revue de code Rust, vous devez expliquer pourquoi vous clonez. Si la réponse est « pour que le compilateur ne se plaigne pas » — préparez-vous à un commentaire cinglant.
\n\n3. Mauvaise gestion des erreurs : .unwrap() n'est pas une solution finale
\nLes seniors détestent unwrap() et expect() dans le code de production. Pourquoi ? Parce qu'ils transforment une erreur potentiellement récupérable en panique. Et une panique en Rust, c'est une terminaison brutale.
Qu'est-ce qui ne va pas avec ce code ?
\nlet file_content = std::fs::read_to_string("config.txt").unwrap();\nSi le fichier n'existe pas — le programme plante. Le senior proposera :
\nlet file_content = std::fs::read_to_string("config.txt")\n .map_err(|e| format!("Failed to read config: {}", e))?;\nUtilisez l'opérateur ? et vos propres types d'erreurs. C'est la base du Rust idiomatique. Lors d'une code review Rust, c'est l'une des premières remarques.
4. Performance oubliée : allocations et itérateurs
\nRust est un langage système. Le senior regardera non seulement la sécurité, mais aussi la vitesse. L'un des problèmes typiques est la création de collections inutiles.
\nExemple : collectionnement inutile
\nlet numbers: Vec<i32> = (0..100).collect();\nlet doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();