La réécriture de Bun en Rust a brassé pas mal de monde dans le petit coin technique d’Internet aujourd’hui. Si vous n’êtes pas du genre à être chroniquement en ligne dans les coins techniques d’Internet, vous avez peut-être manqué ça. Cette histoire contient assez d’appâts pour nous occuper pendant des semaines : Bun, Zig, Rust, Anthropic, 64 instances de Claude et un million de lignes de code générées. Ça aurait dû se transformer en une guerre de langages assez plate, avec un peu d’IA saupoudrée par-dessus. Malheureusement, c’est beaucoup plus intéressant que ça.
Un peu de contexte. Bun est un runtime JavaScript écrit à l’origine principalement en Zig, puis acquis par Anthropic en décembre 2025. En mai, Jarred Sumner a utilisé une version préliminaire de Claude Fable 5 pour porter mécaniquement le code Zig de Bun vers Rust. Selon le récit publié par Bun, environ 50 workflows dynamiques ont roulé sans arrêt pendant 11 jours, avec à peu près 64 instances de Claude qui travaillaient en parallèle au plus fort de l’expérience. Elles ont traduit 1 448 fichiers Zig et plus de 535 000 lignes de code en un diff contenant un peu plus d’un million de nouvelles lignes. Avant le merge, le travail avait consommé l’équivalent d’environ 165 000 $ au tarif de l’API. À la fin, la même suite de tests, indépendante du langage, passait sur les six plateformes prises en charge, sans qu’aucun test soit supprimé ou ignoré. Le port est maintenant disponible dans la version canary de Bun 1.4, ce qui n’est quand même pas la même chose qu’une version stable.
Ce n’est pas un petit refactor de fin de semaine. C’est de la génération de code à l’échelle industrielle.
Ce n’est pas tout à fait juste non plus de dire qu’une IA a simplement réécrit Bun toute seule. Sumner a construit autour d’elle une usine de développement assez élaborée. Un guide de portage associait les patterns Zig à leurs équivalents Rust. Un workflow séparé retraçait les lifetimes dans le code original. Chaque agent d’implémentation était suivi par au moins deux agents de révision adversariale qui travaillaient dans des contextes séparés, puis un autre agent appliquait les corrections. Les erreurs du compilateur devenaient une file de travail distribuée. D’autres boucles s’occupaient ensuite de corriger les tests sur chaque plateforme. Sumner dit avoir surveillé les workflows, lu leurs résultats, ajusté leurs règles, vérifié que les tests roulaient réellement et effectué des validations locales avant le merge.
Le code a été généré par l’IA et principalement révisé par l’IA, mais le processus, lui, a été conçu et supervisé par une personne. Cette distinction est importante. Soixante-quatre instances de Claude ne se sont pas réveillées un matin avec un goût commun et l’envie soudaine d’améliorer un runtime. Une personne a construit une machine qui utilisait les modèles comme travailleurs, puis le compilateur et la suite de tests comme instruments de mesure de l’usine.
Je ne suis pas un AI hater, mais je ne suis pas non plus un AI lover. Traduire une base de code de cette taille, réussir à la compiler et faire passer toute la suite de tests, c’est hallucinant. La méthode est peut-être même plus intéressante que le résultat. On est très loin de demander à un assistant de programmation d’ajouter un endpoint pendant qu’on va se faire un café.
Ça ne veut pas dire que je suis automatiquement à l’aise avec le résultat.
Aujourd’hui, Andrew Kelley, le créateur de Zig, a publié sa réponse à la réécriture de Bun.
Ouch.
Son texte est personnel, direct et visiblement chargé de plusieurs années de frustration entourant la relation entre Bun et le projet Zig. Je comprends pourquoi il l’a écrit de cette façon, mais j’ai aussi peur que le ton rende ses meilleurs arguments beaucoup trop faciles à rejeter. C’est exactement le genre de texte dont les gens vont prendre des captures d’écran pour démontrer que la communauté Zig est hostile, même si les passages les plus importants ont finalement très peu à voir avec la défense de Zig comme langage.
Kelley remet en question l’idée que Bun devait choisir entre des guides de style en Zig et un modèle d’ownership imposé par le compilateur en Rust. Selon lui, l’ingrédient qui manquait était surtout un effort d’ingénierie soutenu : réviser le code, corriger les bogues, rembourser la dette technique, soumettre les parties dangereuses au fuzzing et donner à la stabilité le temps dont elle a besoin. Il remet aussi en question la part des gains de performance et de réduction de la taille du binaire qui revient réellement à Rust, puisque la réécriture s’est faite en même temps que des changements au linker, du travail sur ICU, un grand ménage et une quantité assez extraordinaire d’attention. Il affirme que le LTO était déjà disponible en Zig, conteste le récit de Bun à propos de ses efforts précédents en matière de fuzzing et demande pourquoi l’annonce de la réécriture ne contient aucune comparaison des temps de compilation.
Certaines de ces affirmations reposent sur des conversations privées et sur une relation brisée depuis longtemps. Il faut donc les lire comme la version de Kelley, pas comme des faits neutres. Sa question technique principale est toutefois assez difficile à esquiver : si la suite de tests de Bun n’était pas suffisante pour détecter les problèmes de stabilité dans le code Zig, pourquoi devrait-on considérer que cette même suite de tests constitue une preuve suffisante pour environ un million de lignes de Rust qui n’ont pas été révisées ligne par ligne par des humains?
Il a raison. Une suite de tests au vert ne peut pas certifier à elle seule une réécriture de cette taille.
Mais ce n’est pas toute la réponse, parce qu’une suite de tests et le compilateur de Rust ne vérifient pas les mêmes choses.
Un test demande si le programme a produit le comportement attendu dans un cas que quelqu’un a pensé à couvrir. Même une suite contenant plus d’un million d’assertions demeure une collection finie de situations que quelqu’un a pensé à tester. Plusieurs des bogues décrits par Bun exigeaient une séquence particulièrement malchanceuse : un callback JavaScript réentrant, un buffer détaché pendant une coercition, une opération asynchrone qui se termine après la destruction d’un objet, une allocation qui échoue dans un chemin d’erreur inhabituel ou deux threads qui arrivent précisément dans le mauvais ordre. Si ce chemin n’est jamais exécuté par un test, un sanitizer ou un fuzzer, le bogue reste invisible.
Le safe Rust, lui, pose une autre question : est-ce que cet état d’ownership peut
exister tout court? L’ownership et les emprunts peuvent éliminer des catégories complètes
d’utilisation après libération et de double libération. Drop fait en sorte qu’il
est plus difficile d’oublier le nettoyage dans les différents chemins d’exécution, tandis que Send et Sync empêchent plusieurs data races en safe Rust. Le compilateur applique ces règles partout en safe Rust,
y compris dans les branches qu’aucun test n’exécute. La suite de tests nous dit que le nouveau
Bun se comporte comme l’ancien là où quelqu’un a pensé à regarder. Rust peut empêcher
certaines façons d’échouer, même là où personne n’a pensé à regarder.
C’est la réponse la plus convaincante à la question de Kelley. Les tests ne sont pas
soudainement devenus plus complets. Rust a ajouté un deuxième vérificateur. Les tests servent
d’oracle de régression pour le comportement, tandis que le système de types oblige Bun à
exprimer plusieurs de ses hypothèses sur l’ownership et les lifetimes sous une forme que le
compilateur peut rejeter. Un guide de style demande à chaque personne qui contribue ou révise
le code de se souvenir d’un invariant pour toujours. Rust rend une partie de cette bonne
ingénierie obligatoire à chaque nouvelle compilation, tant que le code concerné demeure en safe Rust.
Ça ne veut pas dire que Rust a prouvé que Bun était correct. Même pas proche.
Bun intègre encore JavaScriptCore ainsi que plusieurs bibliothèques en C et en C++. Au moment
de l’annonce, environ 4 % du code Rust se trouvait dans des blocs unsafe, ce qui
représentait à peu près 27 000 lignes et 13 000 utilisations du mot-clé unsafe.
Bun précise que 78 % de ces blocs ne font qu’une ligne, généralement autour d’un pointeur
provenant du C++ ou d’un appel vers une bibliothèque C. Les frontières de confiance ne se
mesurent toutefois pas en pourcentage. Un seul wrapper incorrect autour d’un bloc unsafe peut invalider les hypothèses faites par une énorme quantité de code safe.
L’article de Bun contient lui-même l’exemple parfait. Une implémentation générée passait une Box de Rust à libuv, qui conservait le pointeur brut pour un callback asynchrone
exécuté plus tard. Rust a ensuite fait exactement ce qu’on lui avait demandé de faire et a
libéré la Box à la fin du scope, laissant la bibliothèque C avec un pointeur
invalide. Le code compilait sans problème. Un agent de révision adversariale a détecté le
risque d’utilisation après libération et de double libération. Drop peut nous empêcher
d’oublier de libérer quelque chose, mais lorsqu’on traverse une frontière FFI, il peut aussi le
libérer trop tôt si on a mal décrit sa durée de vie.
Rust ne peut pas comprendre un contrat qu’on n’a jamais exprimé. Il ne peut pas empêcher les deadlocks, les erreurs de logique liées à la concurrence, les problèmes de réentrance, les bogues d’autorisation, les traversées de répertoires ou l’implémentation incorrecte d’un protocole. Une vérification des limites peut transformer une corruption de mémoire en panique, ce qui est plus sécuritaire, mais une panique déclenchée à distance peut tout de même devenir un problème de déni de service. Rust ne prouve pas qu’un programme est correct. Il refuse simplement une quantité étonnante de façons d’avoir tort.
La réécriture a elle-même introduit 19 régressions connues, que Bun dit avoir toutes corrigées avant l’annonce. Plusieurs provenaient de code Zig et Rust qui avait presque exactement la même apparence, mais qui se comportait différemment. Ce nombre est peut-être incroyablement bas pour un portage de cette taille. Ça pourrait aussi n’être que la portion découverte jusqu’à maintenant. Le mot « connues » fait pas mal de travail ici.
Donc oui, le port Rust améliore probablement le profil de risque de Bun. Il n’élimine pas la surface dangereuse, mais il rend plusieurs opérations non vérifiées explicites et faciles à retrouver, tout en permettant à la majorité du code Rust d’être vérifiée selon les règles du code safe. C’est significatif. Ce n’est pas la même chose que de dire que le port est sécuritaire, sémantiquement identique ou digne de confiance simplement parce que le CI est vert.
Kelley a raison sur un autre point important : ce n’était pas une expérience contrôlée. Le langage a changé, mais le modèle d’ownership, l’organisation du code, la configuration du compilateur et du linker, le travail sur la taille du binaire, l’intensité des audits, les révisions de sécurité, l’infrastructure de fuzzing et la quantité d’attention consacrée à l’ingénierie ont également changé. Bun affirme que la version 1.4 corrige 128 bogues reproductibles dans la version 1.3.14, qu’elle roule environ 2 à 5 % plus rapidement dans ses propres benchmarks et qu’elle est environ 20 % plus petite sur Linux et Windows après avoir combiné la réécriture au travail sur ICU et sur le linker. Ce sont des résultats encourageants, mais Rust n’a pas causé tout ça à lui seul.
Pour comparer honnêtement, il ne faudrait pas opposer la version Zig telle qu’elle existait à la version Rust après une immense campagne de nettoyage. Il faudrait comparer cette dernière à une version Zig ayant reçu exactement le même effort d’ingénierie. Nous n’avons pas cette comparaison. Une partie des améliorations aurait presque certainement pu être réalisée en Zig. L’argument d’Andrew est qu’une meilleure ingénierie aurait pu corriger le code Zig. L’argument de Bun est que Rust rend une partie de cette bonne ingénierie mécaniquement obligatoire. Les deux peuvent être vrais.
C’est aussi là que le désaccord cesse d’être seulement une question de langages et devient une question de ce que les gens recherchent dans la programmation.
Kelley accorde une importance extrêmement élevée au métier d’écrire du code. L’interdiction des contributions assistées par LLM au projet Zig repose en bonne partie sur la rareté du temps de révision et sur la responsabilité à long terme. Les projets open source ne manquent plus de personnes capables de générer un patch plausible. Ils manquent de mainteneurs qui comprennent le patch, peuvent le défendre, vont apprendre pendant la révision et seront encore là quand il va casser quelque chose six mois plus tard. Soixante-quatre agents multiplient la production. Ils ne multiplient pas la responsabilité.
L’approche de Sumner est presque l’inverse. Il optimise pour le produit final. Les modèles sont des travailleurs interchangeables dans un processus, le compilateur et les tests sont les juges, et les utilisateurs de Bun sont la raison de faire tout ça. Ce n’était pas une pull request générée à la va-vite par quelqu’un incapable de l’expliquer. La personne qui dirige le projet a conçu le processus, l’a surveillé et reste responsable de ce qui sera livré. C’est un contexte très différent de celui où l’on inonde un projet open source de patches générés avant de demander à des bénévoles de ramasser les pots cassés. La politique de Zig peut être parfaitement logique, et l’expérience de Bun peut quand même valoir la peine d’être tentée.
J’utilise Zig et Rust professionnellement, mais pas pour les mêmes raisons.
Zig est merveilleux quand le bout difficile m’appartient. Il est excellent pour les parsers, les moteurs de stockage, les structures de données compactes, les formats de fichiers, les allocateurs personnalisés et les produits où le cœur technique est justement la valeur. Très peu de choses se passent dans mon dos. Zig me demande de décider comment les choses vivent et meurent, puis me laisse en assumer les conséquences.
Zig peut absolument être utilisé pour des systèmes qui communiquent sur un réseau, mais son écosystème plus petit et encore en évolution fait en sorte qu’une plus grande partie de TLS, de l’annulation, de la backpressure, du comportement des protocoles et de la durée de vie des connexions risque de devenir mon problème. Si je suis prêt à faire ce travail, le contrôle est fantastique. Si j’essaie de tester une idée de produit avant même de savoir si quelqu’un en veut, ça peut devenir un méchant détour avant d’arriver à une réponse HTTP.
Ça me gosse d’aimer Rust.
Rust me donne une grande partie de ce que je cherche dans Zig, souvent dans la même classe générale de performance, tout en me permettant de rester productif. Son écosystème me laisse construire rapidement des prototypes réseau et des expériences à moitié cuites, pendant que le compilateur empêche plusieurs de mes décisions de lifetime tout aussi à moitié cuites de se transformer en risque de production. Rust ne me permet pas de savoir que mon programme est correct. Il élimine plutôt plusieurs façons particulièrement gossantes et coûteuses de se tromper. Pour les services qui doivent durer, les systèmes concurrents ou connectés au réseau et les logiciels qui touchent à beaucoup de bibliothèques externes, ce compromis est difficile à ignorer.
Ce qui m’amène à la conclusion pratique un peu inconfortable : je peux admirer cette réécriture et quand même décider de ne rien confier d’important à Bun pour l’instant.
Rien ne me permet d’affirmer que le port Rust n’est pas sécuritaire. Il possède une énorme suite de tests, a fait l’objet de onze rondes de révision de sécurité assistée par l’IA, fait maintenant rouler du fuzzing guidé par la couverture 24 heures sur 24 sur ses parsers et est déjà utilisé par Claude Code ainsi que dans la bêta publique de Prisma. Ces faits méritent d’être reconnus. Ils ne remplacent pas pour autant un audit de sécurité humain et indépendant, ni quelques années d’utilisation stable et plate en production.
Le processus me demande de faire confiance à la traduction générée d’un million de lignes, à
une importante surface de code unsafe et C++, à des révisions principalement réalisées
par la même famille de modèles et à une expérience publique dont le succès fait maintenant partie
du récit entourant le produit. C’est plus de confiance que je suis prêt à placer dans un outil de
production pour l’instant. Je peux admirer le coup sans offrir mes propres systèmes comme environnement
de stabilisation.
Le résultat le plus important n’est peut-être pas que l’IA peut écrire un runtime JavaScript. C’est peut-être qu’elle a rendu le choix d’un langage moins permanent. Historiquement, réécrire plus d’un demi-million de lignes aurait paralysé une équipe pendant un an. Le projet n’aurait donc jamais été approuvé. Bun affirme avoir franchi la première barrière de migration en 11 jours pour environ 165 000 $ au tarif de l’API. Si cette approche tient la route, des bases de code matures écrites dans des langages sans sécurité mémoire pourraient migrer vers des fondations plus sûres, même lorsqu’une réécriture humaine ne serait jamais financée. C’est énorme.
Mais le goulot d’étranglement a bougé. Générer du code est devenu bon marché. La confiance, non.
Réécrire du code ne consiste pas seulement à traduire de la syntaxe. Il faut spécifier de nouveau tout ce que les anciens tests n’ont jamais spécifié, et c’est souvent dans ces trous-là que vivent les problèmes de sécurité. Les agents ont été efficaces parce qu’ils disposaient de deux oracles déterministes particulièrement puissants : un compilateur qui refusait certains états invalides et une suite de tests qui rejetait les différences de comportement connues. Tout ce qui échappait au compilateur, aux tests et aux révisions subséquentes pouvait aussi échapper complètement à la boucle de génération.
Andrew Kelley fait bien de résister au récit marketing trop propre selon lequel Rust serait arrivé pour réparer ce que Zig était incapable de régler. Bun a raison de dire que Rust offre des garanties imposées par le compilateur qui changent réellement le problème de maintenance. Et la réécriture demeure un exploit technique extraordinaire, même si on découvre un jour qu’elle était imprudente.
Je peux être impressionné par ce qui a été construit, être d’accord avec la personne qui le critique et quand même décider de ne pas le faire rouler en production. Ces positions ne se contredisent pas. C’est simplement ce qui arrive quand on peut produire du code beaucoup plus rapidement qu’on peut produire de la confiance.
La réécriture a pris 11 jours. La confiance, elle, doit encore rouler en temps réel.
