La réécriture de Bun en Rust : 11 jours, 6 778 commits et ce qui à vraiment changé
L’équipe de Bun a livré quelque chose d’inhabituel en mai 2026 : elle a remplacé tout le cœur natif du runtime par un port en Rust, et l’a fait en 11 jours.
La mise en garde en haut de l’annonce donne le ton. Bun a été racheté par Anthropic en décembre 2025. Jarred Sumner, créateur de Bun, a utilisé une version préliminaire de Claude — un « modèle de classe Mythos » qu’ils appellent Fable 5 — pour une bonne partie du travail. Ce n’est pas un exercice de réflexion. Bun v1.4.0, la première version en Rust, est en canary.
Les chiffres bruts :
- 535 496 lignes de Zig (hors commentaires) sont devenues un code base en Rust
- 6 778 commits entre le 3 et le 14 mai
- 1 448 fichiers
.zigportés mécaniquement en.rs - ~165 000 $ de dépense API (5,9 Mds de tokens d’entrée non mis en cache, 690 M de tokens de sortie)
- Pic : 64 Claudes lancés simultanément sur 4 worktrees

Bun à toujours été un pari sur Zig
Bun a démarré comme un port ligne à ligne du transpileur JS/TS d’esbuild, de Go vers Zig. Sumner à écrit sa première ligne de Zig le 16 avril 2021, après avoir vu la référence du langage Zig en une seule page sur Hacker News. La version initiale — transpileur, minifieur, bundler, gestionnaire de paquets compatible npm, test runner dans l’esprit de Jest, surface d’API Node.js — a été écrite par une seule personne en un an, dans un petit appartement d’Oakland, avant que les LLM ne servent à ce genre de travail.
Le pari a payé. La CLI de Bun dépasse aujourd’hui 22 millions de téléchargements mensuels. Claude Code et OpenCode l’utilisent comme runtime. Vercel, Railway et DigitalOcean proposent un support officiel de première classe.
L’ampleur était aussi le problème.
La dette : mémoire avec GC a côté de mémoire manuelle
JavaScript utilise le garbage collection. Bun embarque JavaScriptCore (le moteur de Safari) plus un tas de bibliothèques C/C++ : uWebSockets, BoringSSL, SQLite, lsquic. Zig, comme C, ne gère pas la mémoire pour vous. Le rôle de Bun est de s’intercaler entre un langage avec GC et du code natif à gestion manuelle, et c’est sur cette frontière que vivaient la plupart de ses pires bugs.
L’annonce liste un échantillon de ce qu’ils ont corrigé dans la seule v1.3.14 : un heap-use-after-free dans node:zlib, un use-after-free dans node:http2 dû à des callbacks réentrantes, des écritures hors bornes dans UDPSocket.sendMany, une fuite mémoire par tls.connect, un double-free dans le parser CSS. La liste continue, et elle est technique.
Sumner précise que ce n’est pas la faute de Zig. « We wouldn’t have gotten this far if not for Zig, and I’ll always be grateful. » Le problème est structurel : gérer les lifetimes à travers une frontière avec GC est difficile dans n’importe quel langage qui n’est pas conçu pour ça, et la réponse de Zig — un defer explicite à chaque point d’appel — se rate facilement dans les chemins d’erreur rarement atteints.
Il a pesé le C++. Environ 20 % de Bun est déjà en C++, et en déplacer davantage aurait apporté constructeurs et destructeurs. Mais cela resterait dépendant de guides de style imposés par la revue de code, et la corruption mémoire continuerait d’arriver.
L’argument de Rust est différent : en Rust sûr, le use-after-free, le double-free et l’« oubli de libérer » sont des erreurs de compilation. Le borrow checker et Drop transforment un problème de guide de style en un problème du système de types.
La stratégie : transpiler, ne pas refondre
Les réécritures ont mauvaise réputation, et pour cause. Une réécriture à partir de zéro de 535 K lignes aurait gelé les fonctionnalités et les correctifs pendant un an. La forme choisie par Bun était un port mécanique : même architecture, mêmes cibles de performance, mêmes fonctionnalités, même suite de tests — juste en Rust.
Deux décisions ont porté tout l’effort :
- Tout d’un coup, pas par incréments. L’expérience de Sumner en portant esbuild vers Zig lui a appris que les réécritures incrémentales laissent un échafaudage temporaire qui gêne plus qu’il n’aide.
- Faire ressembler a du transpilé. L’objectif était un Rust qui se lit comme le Zig dont il vient, pour être refactorisé vers un Rust idiomatique après la sortie de v1.4.
Le point crucial : la suite de tests de Bun est écrite en TypeScript, donc elle se moque du langage dans lequel le runtime est implémenté. Cela leur a permis de traiter « tous les tests au vert » comme la définition du terminé.
La préparation a été courte mais délibérée. Avant d’écrire du code, Sumner a passé ~3 heures avec Claude à mapper les motifs Zig vers Rust ; cette conversation est devenue PORTING.md (qui est ensuite ressorti sur Hacker News). Une deuxième passe a analysé les lifetimes Rust corrects de chaque champ de chaque struct du code base et les à sérialisés dans LIFETIMES.tsv. Les deux documents ont subi une revue adversarial avant qu’une seule ligne ne soit portée.
Ce qui est resté en place : JavaScriptCore et les bibliothèques C/C++ embarquées. La réécriture concernait la colle native et le code spécifique à Bun, pas le moteur JS.
Exécution : 64 Claudes, 11 jours
Le port a tourné sous la forme d’environ 50 « dynamic workflows » dans Claude Code, en continu, pendant 11 jours. Chaque workflow était une boucle : prendre une tâche, produire du code, se faire relire, appliquer le retour.
Le modèle de revue est la partie intéressante. Pour chaque implémenteur, il y avait deux ou plus de relecteurs adversarial — des sessions Claude séparées chargées de supposer que le code est faux et de trouver chaque raison pour laquelle il échoue. L’implémenteur ne relit jamais ; le relecteur n’implémente jamais. L’annonce le présente comme le miroir de la revue humaine : l’auteur veut merger, donc une autre partie vérifie.
Quelques mécanismes méritent d’être tirés au clair :
- Essai d’abord. Ils ont porté 3 fichiers avant de s’engager sur les 1 448.
- Sharding par worktree. Les premières exécutions avaient des Claudes qui s’écrasaient les uns les autrès avec
git stashetgit reset. La solution : 4 worktrees, 16 Claudes chacun, avec la règle de ne jamais lancer git ni cargo en pleine tâche. - Les erreurs de compilation comme file de travail.
cargo checka déversé ~16 000 erreurs dans un fichier, groupées par crate, et 64 Claudes les ont grignotées — 16 boucles fix/review/apply sur 4 worktrees. - Dépendances cycliques. Zig était effectivement une seule unité de compilation ; Rust en avait besoin d’environ 100. Démêler les cycles a fait remonter la plupart de ces 16 000 erreurs.
- Isolement. Les tests de strèss qui épuisent les sockets TCP ou lancent ~10 k processus tournaient sous des cgroups de
systemd-run. La machine à tout de même manqué de disque et planté quelques fois.
timeline
title La réécriture Zig → Rust de Bun en 11 jours (mai 2026)
2026-05-03 : Branche de port ouverte (PR #30412)
2026-05-04 : Premier lot d'ébauche de 100 fichiers
2026-05-06 : ~16 000 erreurs de compilation, 64 Claudes
2026-05-08 : Première exécution de CI (972 fichiers en échec)
2026-05-09 : Linux x64 passe au vert
2026-05-11 : Windows au vert (dernière plateforme)
2026-05-14 : Les 6 plateformes au vert, merge
Les chiffres : pic à 1 300 lignes de code par minute, 695 commits dans l’heure la plus chargée (6 mai), 58 commits dans la minute la plus chargée. Chaque commit a été relu par deux relecteurs adversarial avant d’être intégré. Le diff final était de +1 009 272 lignes. Aucun test n’a été sauté ni supprimé.
C’est en CI que ça est devenu réel. Deux jours après la première exécution, les fichiers de test en échec sont passés de 972 à 23. Un jour et demi plus tard, Linux était entièrement vert. Windows a fini en dernier le 11 mai ; les six plateformes étaient vertes au build #54202, le 14 mai.
Ce que Rust à vraiment apporté
L’objectif affiché était la stabilité, et le billet le confirme par des chiffres durs.
Bugs. Bun v1.4.0 corrige 128 bugs qui se reproduisent en v1.3.14.
Mémoire. Drop a remplacé le defer par point d’appel. L’exemple du billet : bundler le même projet de 60 modules 2 000 fois dans un seul processus. En v1.3.14, chaque build fuit ~3 Mo pour toujours ; en v1.4.0, la mémoire se stabilise.
| Builds | v1.3.14 | v1.4.0 |
|---|---|---|
| 500 | 1 914 MB | 526 MB |
| 1 000 | 3 506 MB | 586 MB |
| 1 500 | 5 097 MB | 608 MB |
| 2 000 | 6 745 MB | 609 MB |
Une tentative précédente de corriger ça en Zig n’a jamais été mergée, écrit Sumner, car l’absence d’équivalent à Drop rendait difficile d’être sûr.
Taille du binaire. La réécriture en Rust seule a supprimé 3,8 Mo (Windows), 5,5 Mo (macOS), 6,8 Mo (Linux) — surtout en abandonnant l’excès de comptime Zig. Un travail supplémentaire de linker (identical code folding, élagage des données ICU) a porté le gain total à ~20 % sur Linux et Windows.
| Version | Plateforme | Taille |
|---|---|---|
| v1.4.0 | Windows | 76 MB |
| v1.3.14 | Windows | 94 MB |
| v1.4.0 | Linux | 70 MB |
| v1.3.14 | Linux | 88 MB |
Vitesse. Le LTO entre langages C/C++ et Rust permet au compilateur d’inliner à travers la frontière des langages. Bun a mesuré des gains de 2–5 % :
Bun.serve: 169,6k → 177,7k req/s (+4,8 %)express: 64,5k → 66,6k (+3,2 %)next build: 13,62s → 13,03s (+4,5 %)tsc -b --force: 0,94s → 0,89s (+4,7 %)
Prisma a basculé sa bêta publique Compute sur le port en Rust. Alexey Orlenko : « We ran into memory leaks and à connection pool that couldn’t recover after à VM was paused and resumed. When the Rust rewrite appeared, we tested it against the same failure modes. It handled them perfectly. »
Claude Code v2.1.181 (17 juin) et les suivants utilisent le port en Rust. Le démarrage est 10 % plus rapide sur Linux. Le verdict de Sumner sur le changement visible pour l’utilisateur : « Boring is good. »
Là où le port a dérapé
Un transpile fidèle de 535 K lignes n’est pas gratuit. Le billet documente 19 régressions connues, toutes corrigées. Les plus instructives sont de petits écarts sémantiques entre langages :
- Effets de bord de
debug_assert!. Leassertde Zig est une fonction, donc son argument s’exécute à chaque build. Ledebug_assert!de Rust est une macro effacée en release — un appel àinsert_stalequi mutait l’état HMR à simplement cessé de s’exécuter. (#30678) - Tranches de longueur impaire. Le helper de Zig ignorait un octet impair final ;
bytemuck::cast_sliceplante dessus.Blob.text()sur un BOM UTF-16 plus un nombre impair d’octets a commencé à crasher. (#31188) - Vérifications de bornes. Le
ReleaseFastde Zig les retire ; Rust les garde. Une constante placeholder a fait chuter le plafond d’interning des noms de fichiers de 8,4 M à 270 K, et de vrais projets l’ont heurté. (#31503) - Chaînes de format
comptime. Zig évalue les chaînes de format à la compilation, donc les marqueurs de couleur disparaissent avant la substitution des arguments. Rust n’a pas decomptime, alors le parser de marqueurs a avalé un antislash littéral dans les hyperliens OSC 8. (#30693)
Ce sont les bugs qui ne sortent que parce que deux langages se ressemblent sans l’être.
Ce que ça veut dire
Enlevant l’angle LLM, il y à une vraie histoire d’ingénierie : un projet a heurté le plafond de ce que la gestion manuelle de la mémoire pouvait offrir à grande échelle, et a choisi le borrow checker de Rust comme l’outil pour rendre impossible toute une classe de bugs plutôt que de la simplement décourager.
L’angle LLM est la partie sur laquelle on discutera. Sumner est franc : cela aurait pris a trois ingénieurs ayant le contexte complet du code base environ un an, et ils ne l’auraient jamais fait — l’alternative réaliste était « ne rien faire et continuer à corriger les bugs ». À la place, un ingénieur supervisant 64 Claudes l’a fait en 11 jours pour ~165 K $.
Environ 4 % du code Rust de Bun se trouve dans des blocs unsafe (~13 000 mots-clés unsafe sur ~27 000 lignes, pour ~780 000 au total), et 78 % de ces blocs tiennent sur une seule ligne. Dépuis le merge, ils ont mené 11 tours de revue de sécurité et ajouté un fuzzing couverture-dirigée 24/7 sur tous les parsers — 100 milliards d’exécutions à ce jour, ~15 PRs.
La question de la maintenabilité est celle que je surveillerais. Sumner dit que le Rust se lit comme le Zig, et montre un côte à côte de canMergeSymbols pour le prouver. Le pari est qu’un port fidèle se relit port par port, ce qui a permis à une personne de valider un diff d’un million de lignes.
Sa phrase finale est celle qui vieillira de façon intéressante : « One engineer can do à lot more today than à year ago. »