Leçons du portage de Bun vers Rust piloté par l’IA : la vérification est le véritable travail
Ce que le mouvement assisté par l’agent de Bun, allant de Zig à Rust, nous apprend sur les ensembles de tests en tant que contrats, les guides de portage, le code non sécurisé, et pourquoi la vérification limite aujourd’hui l’écriture de code par l’IA.
Sources: Bun Rewrote Its Runtime in Rust With AI. The Real Story Is Bigger Than the Code
La plupart des équipes d’ingénierie évitent autant que possible de réécrire en langage une grande application en production. Bun, l’environnement d’exécution et l’outil pour JavaScript et TypeScript, aurait transféré sa mise en œuvre de Zig à Rust, les agents de codage basés sur l’IA s’occupant de la majeure partie de la traduction, et les chiffres publiés sont impressionnants. Cependant, l’aspect le plus intéressant n’est pas la quantité de code générée par ces agents, mais plutôt la manière dont le processus associé a déterminé quel code pouvait être considéré comme fiable. Cet article décrit ce processus et le transforme en pratiques que vous pouvez appliquer à vos propres refacturations et migrations assistées par l’IA.
Pourquoi les réécritures sont généralement évitées
La réécriture d’un langage comporte une liste bien connue de risques. Elle peut entraîner des régressions subtiles que personne ne remarque pendant des mois, modifier les performances de manière inattendue, casser une plateforme utilisée uniquement par un petit nombre d’utilisateurs, et absorber une grande partie de la capacité d’une équipe. Dans le pire des cas, l’équipe se retrouve à maintenir en même temps l’ancienne et la nouvelle implémentation, payant deux fois pour chaque correction de bug.
C’est pourquoi la migration vers Bun attire l’attention. Au moment de la rédaction, le dépôt du projet décrit Bun comme un environnement d’exécution JavaScript basé sur Rust et construit sur JavaScriptCore, et Anthropic a annoncé qu’elle allait acquérir Bun en décembre 2025. Selon les rapports publics, le portage correspond à environ 535 000 lignes de code Zig traduites en l’espace d’environ 11 jours, les agents s’occupant de la majeure partie du travail d’implémentation au sein d’un processus strictement défini.
Il est tentant de voir cela comme une autre manchette du type « L’IA a écrit une montagne de code ». La question plus pertinente est différente : un système d’IA peut-il apporter des changements importants lorsque les humains mettent en place un mécanisme solide pour vérifier si le résultat est correct ? Cette question a bien plus d’importance pour la manière dont les logiciels seront développés au cours des prochaines années.
Un port, pas une refonte
Bun a été initialement écrit en grande partie en Zig, un langage système apprécié pour son contrôle de bas niveau et ses performances prévisibles. La migration n’a pas été présentée comme un nouveau départ. Son objectif était délibérément restreint : conserver l’architecture existante et le comportement observable, ne modifiant que le langage dans lequel l’implémentation est écrite.
Cette approche constitue la décision de conception clé. Une réécriture conventionnelle incite les ingénieurs à redessiner en cours de route, en changeant les structures de données, en réorganisant les modules, en ajoutant des abstractions et en ajustant les API. Chacun de ces choix représente une nouvelle source de risque ainsi qu’un élément supplémentaire à examiner. En éliminant la plupart d’entre eux, l’équipe de Bun a transformé une tâche créative ouverte en une tâche mécanique dotée d’un point de référence clair : l’implémentation actuelle et les tests qui la décrivent.
Le nombre de lignes semble être l’aspect le plus difficile, mais ce n’est pas le cas. Produire du code Rust syntaxiquement valide est relativement aisé. C’est plutôt garantir que le Rust se comporte exactement comme le Zig qu’il remplace qui représente toute la difficulté.
Le jeu de tests en tant que contrat comportemental
Imaginez un agent qui ne reçoit que l’instruction « réécrivez cette base de code en Rust ». Cette consigne laisse toutes les décisions importantes ouvertes : quels éléments peuvent changer et lesquels doivent rester identiques ? Quelles raccourcis sont acceptables ? Quels comportements mémoire sont essentiels ? Et, surtout, comment savoir si le résultat est correct ?
Bun disposait déjà de la réponse à cette dernière question : un ensemble de tests important et mature. Une fois que les tests sont considérés comme un contrat, l’agent n’invente plus d’implémentation ; il reproduit simplement un comportement déjà décrit sous forme exécutable, et chaque fichier qu’il génère soit respecte ce contrat, soit ne le fait pas.
Que peuvent et ne peuvent pas promettre les tests
Un ensemble de tests n’est pas une preuve de correction. Il ne vérifie que les comportements que quelqu’un a jugé nécessaire d’implémenter, et un fichier traduit peut réussir tous les tests tout en présentant des différences dans une voie d’accès que personne n’a testée. Ce que les tests offrent, c’est un mécanisme de retour d’information bien plus fiable que de demander à un modèle d’évaluer sa propre sortie. Sans eux, une réécriture générée peut sembler entièrement plausible tout en modifiant discrètement sa sémantique. Avec eux, chaque modification générée doit justifier son existence.
Le flux de travail Bun décrit comporte plusieurs étapes superposées : la compilation, l’ensemble de tests, les vérifications sur les plateformes prises en charge, l’examen du code et une validation supplémentaire. Le modèle génère des candidats ; le processus d’ingénierie décide lesquels sont retenus. C’est cette division du travail qui constitue l’idée fondamentale à retenir.
Pourquoi le guide de portage était aussi important que le modèle
Au moment où les agents ont commencé à traiter des milliers de fichiers, le projet aurait rédigé un guide détaillé de portage expliquant comment les constructions Zig devaient être exprimées en Rust. Ce guide abordait les mappages de types, les conventions de nommage, les schémas de traduction récurrents, la manière de gérer les dépendances ainsi que des limites explicites concernant les actions autorisées aux agents.
L’objectif est de remplacer une demande vague (“écrire ce fichier en Rust”) par une demande plus cadrée : convertir le fichier en utilisant les mappages convenus, conserver son comportement observable inchangé et ne pas modifier l’architecture. Les agents fonctionnent beaucoup mieux dans un tel environnement, car l’éventail des réponses acceptables est restreint et bien défini.
Un guide permet également de faciliter l’évaluation. Au lieu de la question floue “ça a l’air bon ?”, un examinateur peut poser deux questions concrètes :
- Ce code Rust se comporte-t-il de la même manière que le Zig qu’il remplace ?
- L’agent a-t-il suivi les règles de traduction convenues par le projet ?
Les deux questions ont des réponses qui peuvent être vérifiées, ce dont a besoin un processus d’évaluation lorsque le volume des modifications est énorme.
Ce qu’un guide de portage utile contient
Si vous prévoyez une migration similaire, même à une échelle bien plus petite, un guide de ce type précise généralement :
- la manière dont chaque type source, conteneur et mécanisme d’erreur est traduit dans le langage cible
- les règles de nommage, afin que le code généré soit cohérent dans tous les fichiers et entre les agents
- quelles dépendances peuvent être introduites et lesquelles sont interdites
- les schémas que l’agent ne doit pas utiliser, même s’ils permettraient à un fichier de se compiler
- ce qui constitue une « fin de travail » pour un fichier ou un module donné
La leçon plus générale s’applique bien au-delà de Bun : le système d’instructions autour d’un agent peut influencer la qualité du résultat presque autant que le choix du modèle. Les équipes qui gèrent déjà des fichiers d’instructions pour agents reconnaîtront ce schéma ; notre guide sur la rédaction d’un fichier CLAUDE.md efficace aborde le même principe pour le travail quotidien.
Rust fidèle ne signifie pas toujours Rust idiomatique
Tout ceux qui s’attendent à ce que le code traduit ressemble à une conception en Rust écrite de zéro seront déçus, et c’est intentionnel. Une portée fidèle doit ressembler à l’original dont elle est issue.
Cela entraîne un compromis visible. Zig permet d’exprimer certaines opérations de bas niveau de manière concise, tandis que Rust a tendance à être explicite quant aux types, aux durées de vie, au principe de propriété et aux limites du code non sécurisé. Une fonction Zig compacte peut donc se transformer en une version Rust nettement plus longue. Cette longueur supplémentaire est le résultat d’une optimisation visant l’équivalence de comportement plutôt que l’élégance, et non une preuve que le résultat est défectueux.
Il est utile de distinguer deux affirmations :
- « Ce n’est pas le Rust que j’aurais écrit de zéro. »
- « Ce Rust est incorrect. »
La première relève d’un jugement de style ; la seconde constitue un défaut. Les confondre conduit à des critiques injustifiées concernant les adaptations mécaniques.
D’abord l’adaptation, ensuite le nettoyage
La méthode décrite traitait la traduction mécanique et le perfectionnement ultérieur comme des phases distinctes. Il fallait d’abord faire en sorte que Rust se comporte comme Bun, puis améliorer Rust. En divisant ainsi le travail, l’équipe n’a jamais à gérer en même temps deux modifications importantes et interactives. Si une régression apparaît lors du nettoyage, on peut l’attribuer au nettoyage lui-même, car la version de référence avait déjà été validée avec les mêmes tests.
Lire correctement les blocs unsafe
Les observateurs qui ont examiné la portée ont remarqué un grand nombre de blocs unsafe et l’ont interprété comme un signe d’une qualité médiocre du code Rust. Cette réaction est compréhensible, mais elle ignore comment le code en est arrivé là.
Rust exige que les opérations potentiellement dangereuses, telles que la déréférenciation de pointeurs bruts ou l’appel à des fonctions externes, soient explicitement marquées avec unsafe. Zig ne dispose pas d’un marqueur équivalent. Le code original de Bun est rempli de manipulations mémoire de bas niveau, de traitements de pointeurs et d’appels à des composants en C et C++, ce qui fait que, dans une traduction fidèle, une grande partie de ces éléments apparaît naturellement comme unsafe en Rust. De plus, Bun intègre JavaScriptCore et interagit avec d’autres bibliothèques natives ; la frontière entre le code Rust et celui en C ou C++ exige par nature des opérations unsafe.
En d’autres termes, ce portage n’a pas ajouté de danger ; il a simplement rendu visible le danger existant. Un simple comptage des blocs unsafe ne dit pas grand-chose sur la qualité du code. Des questions plus utiles sont :
- Pourquoi cette opération en particulier doit-elle être
unsafe? - La région
unsafeest-elle suffisamment petite pour être gérée de manière cohérente ?
Ces questions fournissent également un plan d’action naturel pour la phase de nettoyage : réduire les zones non sécurisées, les envelopper dans des abstractions sûres et documenter les invariants sur lesquels chacune repose.
Lorsque la traduction révèle des améliorations
Une adaptation littérale peut encore mettre en évidence des opportunités que la conception originale avait masquées. Un exemple rapporté concernait un parcours de code où l’implémentation en Zig devait passer par C++ pour obtenir certaines informations. En Rust, les données locales au thread concernées pouvaient être accessées plus directement, en évitant cet appel intermédiaire.
La conclusion n’est pas que Rust surpasse Zig en termes de vitesse ; une telle généralisation ne résisterait pas à un examen approfondi. L’idée est plus précise et plus utile : le fait de déplacer du code entre différents langages oblige quelqu’un, ou quelque chose, à examiner attentivement chaque chemin d’appel, et cet examen révèle parfois des tâches qui n’étaient jamais nécessaires. De telles constatations méritent d’être évaluées cas par cas, et non intégrées dans un débat sur les langages.
Où s’est déplacé l’effort d’ingénierie
La migration n’a pas éliminé le travail d’ingénierie ; elle l’a simplement déplacé. Dans le développement traditionnel, une grande partie du temps des développeurs est consacrée à la saisie du code d’implémentation. Dans le développement assisté par des agents, une plus grande partie de ce temps est orientée vers :
- la définition des contraintes et des règles de traduction
- la conception du flux de travail dans lequel opèrent les agents
- l’écriture et le renforcement des tests
- la revue des modifications générées
- le diagnostic des pannes
L’ingénieur devient responsable du système qui génère et vérifie le code plutôt que de chaque ligne de code elle-même. Pour un grand projet, ce cycle se déroule approximativement ainsi : spécification, puis génération, puis compilation, puis test, puis revue, puis correction, et ainsi de suite. Le modèle participe à plusieurs de ces étapes, mais la conception du cycle détermine si le résultat a de la valeur. C’est ce flux de travail, et non le nombre de lignes, qui constitue la véritable innovation.
Pourquoi Bun était un bon candidat
Tout codebase ne se prête pas à cette approche, et Bun présentait plusieurs avantages :
- Une implémentation fonctionnelle servant de référence
- Une architecture connue et stable
- un comportement observable grâce à un ensemble de tests existant
- une langue cible clairement choisie
- un code qui peut être divisé en fichiers et composants relativement indépendants
- un objectif unique et mesurable : conserver le programme tout en changeant la langue
Comparez cela à demander à un agent de concevoir un nouveau produit, où il n’existe pas d’implémentation de référence et où ce qui est « correct » relève de l’opinion. La règle générale est que plus le comportement souhaité peut être spécifié avec précision, plus une grande partie de l’implémentation peut être automatisée. Cela ne signifie pas que les agents peuvent prendre en charge n’importe quel projet de grande envergure. Cela signifie que l’automatisation fonctionne le mieux lorsque la destination peut être mesurée.
Ce que la migration ne prouve pas
Il est facile de surestimer un résultat de ce type, il est donc utile d’être clair sur ses limites. Le port Bun ne prouve pas que l’intelligence artificielle peut remplacer les ingénieurs logiciels. Il ne montre pas non plus que chaque base de code peut être traduite de cette manière, que le code généré est automatiquement maintenable ou que Rust est toujours plus rapide que Zig. Et il ne justifie certainement pas l’intégration de demandes de fusion générées par l’IA en grande quantité dans du code produit ordinaire.
Si cela prouve quelque chose, c’est le contraire. Le travail dépendait de tests rigoureux, de règles explicites, de validations, d’examens, de débogage et d’une supervision humaine continue. L’analyse du flux de travail publié indique également que les agents n’étaient pas laissés fonctionner sans surveillance ; des personnes suivaient le processus et l’ajustaient en cas de problèmes. Le jugement technique n’a pas perdu d’importance ; il est même devenu le fondement sur lequel tout repose.
La vérification devient le goulot d’étranglement
Pendant des décennies, l’écriture de code a été considérée comme l’étape la plus lente. Supposons qu’un agent puisse produire 10 000 lignes en le temps qu’un développeur mettait autrefois pour en écrire 500. La réaction évidente est de célébrer cette accélération, jusqu’à ce que se pose la question suivante : qui va vérifier ces 10 000 lignes ?
La génération de code est désormais peu coûteuse. Vérifier sa correction reste cependant cher, et les coûts augmentent fortement pour les environnements d’exécution, les compilateurs, les bases de données, les systèmes d’exploitation et les codes sensibles à la sécurité, où une petite erreur peut provoquer un échec majeur. Cela souligne l’importance croissante de tout ce qui permet de vérifier plutôt que de générer : suites de tests, capacités d’observation, analyse statique, méthodes formelles, environnements isolés et revues par des experts adverses. Les équipes qui bénéficieront le plus des agents de codage seront probablement celles disposant des systèmes de vérification les plus solides, et non nécessairement celles ayant accès au meilleur modèle.
Pratiques à emprunter pour vos propres modifications assistées par l’IA
Vous n’avez pas besoin d’un projet de taille équivalente à celle du runtime pour appliquer ces leçons. Elles fonctionnent tout aussi bien pour migrer un service entre des frameworks ou convertir une base de code JavaScript en TypeScript.
Mettre en place des tests avant le grand changement
Un ensemble de tests complet constitue une spécification exécutable. Plus vous pouvez vérifier automatiquement de comportements, plus vous pouvez confier en toute sécurité du travail d’implémentation. Si le taux de couverture est faible, l’améliorer constitue la première étape de la migration, et non une considération ultérieure.
Écrire les règles par écrit
Évitez des instructions du type « réécrivez ceci ». Précisez l’architecture, la nomenclature, les dépendances autorisées et interdites, les raccourcis interdits, les attentes en matière de gestion des erreurs ainsi que les critères d’acceptation pour chaque unité de travail.
Séparer la génération de l’examen
Pilote sur un petit échantillon représentatif
Mettez d’abord le processus en œuvre sur un petit nombre de fichiers typiques. Les hypothèses erronées sont faciles à corriger lorsqu’elles affectent dix fichiers, mais très coûteuses lorsqu’elles se retrouvent dans dix mille fichiers.
Considérez le code généré comme non fiable tant que cela n’a pas été prouvé
Un code qui se compile n’est pas nécessairement un code qui fonctionne. La compilation n’est qu’une étape parmi plusieurs, et non une preuve de correction.
Faites en sorte que les personnes soient responsables des décisions importantes
Les agents peuvent explorer très rapidement un vaste éventail d’implémentations possibles. Déterminer ce que le système doit faire et quels risques sont acceptables reste une responsabilité humaine.
Au-delà du débat Zig contre Rust
Il est tentant de présenter cette histoire comme un affrontement entre Zig et Rust, entre du code manuscrit et de l’IA, ou encore entre les humains et les agents. Aucune de ces interprétations ne donne beaucoup d’explications. Le port Bun doit plutôt être compris comme une expérience sur la manière dont le logiciel est développé lorsque la production de code coûte presque rien. Une fois qu’un système peut traduire des centaines de milliers de lignes, la question centrale cesse d’être « pouvons-nous l’implémenter ? » pour devenir « pouvons-nous mettre en place un processus fiable qui nous indique si l’implémentation est correcte ? » Ce sont là des problèmes d’ingénierie très différents.
Comment évaluer le résultat
La traduction initiale n’est que la première étape. Une base de code transférée mécaniquement peut, avec le temps, devenir plus conforme aux bonnes pratiques : les zones à risque sont auditées et réduites, les abstractions améliorées, les performances optimisées et les couches de compatibilité supprimées. Comme tout système en production, il sera finalement évalué sur la base de son efficacité pour les utilisateurs réels. Le matériel source indique que Bun a continué à publier des mises à jour après la migration ; consultez le répertoire du projet ainsi que son journal des modifications pour connaître son état actuel.
L’évaluation réelle proviendra de signaux à long terme plutôt que de la taille de la refonte :
- si le temps d’exécution reste stable d’une version à l’autre
- si le code devient plus facile à maintenir
- si les performances restent compétitives
- si les bugs liés à la mémoire deviennent moins fréquents
- si les contributeurs peuvent comprendre et améliorer le nouveau code
Points clés
- La valeur de la migration Bun réside dans le cycle de règles, de tests, d’examen et de correction, et non dans le volume de code généré.
- En présentant une réécriture comme un portage conservateur du comportement, on offre aux agents un objectif mesurable et aux examinateurs une liste de contrôle concrète.
- Un guide de portage réduit l’éventail des résultats acceptables et rend possible un examen à grande échelle.
- Un Rust verbeux ou fortement basé sur unsafe dans un portage fidèle représente souvent le prix à payer pour l’équivalence ; évaluez le code unsafe en fonction de ses limites, et non de son nombre.
- Au fur et à mesure que la génération devient moins coûteuse, c’est l’investissement dans la vérification qui permettra aux équipes de se distinguer, et la conception de ce cycle de feedback devient de plus en plus le cœur du travail d’ingénierie.