Frontier · Bun Blog
Réécrire Bun en Rust
Jarred Sumner explique pourquoi et comment le bundler de Bun a été réécrit de Zig vers Rust, y compris les boucles de codage agent, l'examen contradictoire, les tests et le travail de performance derrière la migration.

Cet article est traduit du texte original anglais.
Édition texte : les graphiques de données, visualisations interactives et schémas de processus de la source ne sont pas reproduits ici. Consultez l’original pour les visuels complets.
Divulgation : Bun a été acquis par Anthropic en décembre 2025. Moi et d'autres membres de l'équipe Bun travaillons chez Anthropic. J'ai utilisé une version préliminaire de Claude Fable 5 pour une grande partie de la réécriture de Rust.
Bun a démarré en tant que portage ligne par ligne du transpilateur JavaScript et TypeScript d'esbuild de Go à Zig. J'ai écrit ma première ligne de Zig le 16 avril 2021. J'ai parié sur Zig après avoir vu la Zig Référence du langage d'une seule page sur Hacker News et après avoir été vraiment enthousiasmé par le contrôle de bas niveau et le souci des performances.
Dès le début, la portée de Bun était énorme :
- JavaScript, TypeScript et transpilateur, minificateur et bundler CSS
- Gestionnaire de packages compatible npm
- Jest-like test runner
- Résolution des modules compatibles Node.js et TypeScript
- Client HTTP/1.1 et WebSocket
- Implémentations d'API Node.js comme
fs,net,tlset des dizaines d'autres modules
La version initiale de Bun a été écrite par moi en 1 an, dans un appartement exigu d'Oakland, avant LLM, en Zig. Le résultat par défaut pour les projets ambitieux comme Bun est de rejoindre le cimetière des projets morts sur une page de profil GitHub. Zig a rendu Bun possible. Je n'aurais jamais pu construire autant en 1 an sans Zig.
Aujourd'hui, la CLI de Bun reçoit plus de 22 millions de téléchargements mensuels. Des outils populaires comme Claude Code et OpenCode misent sur Bun comme environnement d'exécution. Vercel, Railway, DigitalOcean et bien d'autres bénéficient d'un support propriétaire pour Bun.
Bun a également constitué un défi pour la stabilité. Voici un petit échantillon de bugs que nous avons corrigés dans Bun v1.3.14 :
- plantage du tas-use-after-free dans
node:zliblors de l'appel de.reset()sur un flux zlib, Brotli ou Zstd alors qu'un.write()asynchrone est toujours en cours sur le pool de threads - plantage d'utilisation après libération dans
node:zliblorsqu'un rappelonerrora émis unwrite()réentrant suivi declose()sur les descripteurs natifs - use-after-free plante dans
node:http2lorsque des rappels JS réentrants (par exemplesession.request()dans un écouteur de délai d'attente, un getter d'options ou un rappel d'écriture) déclenchent une nouvelle hachage de hashmap, invalidant les pointeurs de flux internes - use-after-free dans
UDPSocket.send()etsendMany()où le code utilisateur dans les rappelsvalueOf()outoString()pouvait détacher unArrayBufferentre la capture de la charge utile et l'envoi réel - plantage et lecture hors limites dans
Buffer#copyetBuffer#filllorsqu'un rappelvalueOfdétache ou redimensionne leArrayBuffersous-jacent lors de la coercition d'argument - écriture hors limites du tas dans
UDPSocket.sendMany()lorsque l'état de connexion du socket a changé en cours d'itération via les rappels JS de l'utilisateur - Fuite de mémoire dans
crypto.scryptoù les tampons de rappel et de mot de passe/sel protégés n'ont jamais été libérés lorsque l'allocation du tampon de sortie a échoué SSLWrapper.inita divulgué la phrase secrète strdup sur les chemins d'erreur- Fuite de mémoire dans
tlsSocket.setSession()où chaque appel a perdu unSSL_SESSION(~6,5 Ko par appel) en raison d'unSSL_SESSION_freemanquant aprèsd2i_SSL_SESSION - Fuite de mémoire où les
fs.watch()observateurs n'ont jamais été récupérés après.close(), provoquée par un dépassement du nombre de références qui a épinglé de manière permanente chaque observateur en tant que racine GC - plantage doublement gratuit dans l'analyseur CSS lorsque
background-clipavait des préfixes de fournisseur et des arrière-plans multicouches DuplexUpgradeContextn'a jamais été libéré – une fuite complète partls.connect({ socket: duplex })- plantage de condition de concurrence dans
MessageEventoù le thread du marqueur GC a pu observer une variante déchirée dansm_datalors d'un accès simultané à partir d'unBroadcastChannelou d'unMessagePort
Nous aurions pu continuer à corriger ce type de bugs de manière ponctuelle et perpétuelle, mais nous devons à nos utilisateurs de compter sur nous pour faire mieux que cela et empêcher systématiquement que ce type de bugs ne se reproduise.
Ce que nous faisions déjà
- Nous avons corrigé le compilateur Zig pour ajouter la prise en charge d'Address Sanitizer. Nous exécutons notre suite de tests avec ASAN à chaque commit.
- Nous livrons Zig builds ReleaseSafe dont la sécurité a été vérifiée pour Windows
- Nous fuzzons les API d'exécution de Bun 24h/24 et 7j/7 à l'aide de Fuzzilli, le fuzzer du moteur JavaScript utilisé par V8 et JavaScriptCore
- Nous effectuons de nombreux tests de fuite de mémoire de bout en bout
C'est plus que ce que font de nombreux projets.
Soyez vraiment intelligent et ne faites pas d'erreurs ?
Notre liste de corrections de bugs n'était pas bonne et j'en avais assez de m'endormir en m'inquiétant des crashs dans Bun. Je ne blâme pas Zig pour cela - les autres utilisateurs de Zig n'ont pas les bugs que nous avons eu, et mélanger GC avec de la mémoire gérée manuellement est une chose assez rare pour qu'un logiciel ait besoin qu'aucun langage ne soit vraiment conçu pour cela. Nous ne serions pas arrivés aussi loin sans Zig, et je vous en serai toujours reconnaissant. Jusqu'à tout récemment, le choix du langage de programmation était une décision à sens unique pour un projet comme Bun.
JavaScript est un langage de récupération de place et les moteurs JavaScript modernes comme JavaScriptCore (et V8) ont des règles strictes concernant la gestion des exceptions et le garbage collector. Zig, comme C, ne gère pas la mémoire à votre place et c'est un compromis qui, pour de nombreux projets, constitue une excellente raison d'utiliser Zig. Zig n'a pas de constructeurs/destructeurs, et la plupart des nettoyages devraient être écrits explicitement sur chaque site d'appel avec defer.
Pour Bun, la gestion correcte des durées de vie des valeurs récupérées et des valeurs gérées manuellement a été une source majeure de problèmes de stabilité - le plus souvent de petites fuites de mémoire et occasionnellement des plantages. Chaque allocation de mémoire doit être méticuleusement revue. Où ces octets sont-ils libérés ? Comment pouvons-nous nous assurer qu’il ne sera libéré qu’une seule fois ? Avons-nous vérifié correctement les exceptions JavaScript ? Ce pointeur récupéré est-il visible pour le scanner de pile conservateur ? S'agit-il d'une mémoire récupérée ou d'une mémoire gérée manuellement ?
Pour les problèmes de stabilité, il est préférable de le savoir le plus tôt possible. Le fuzzing se produit après la fusion du code. CI se produit lorsque le code est poussé. Les contrôles de sécurité à l'exécution et le nettoyage des adresses se produisent lorsque le code est exécuté (espérons-le en développement, avant CI).
Une manière courante de réduire ce type de problème consiste à garantir que le code de nettoyage est toujours exécuté exactement une fois pour le code qui en a besoin. Zig est conçu pour être un langage simple sans flux de contrôle caché, et il préfère donc le mot-clé explicite defer pour exécuter du code à la fin d'une portée plutôt que le ~Destructor implicite de C++ ou le Drop implicite de Rust.
| Langue | Nettoyage |
|---|---|
| Zig | defer, errdefer |
| C++ | ~Destructeur, &&Déplacement |
| Rust | Laisser |
Pour le code Zig, quand exactement devons-nous exécuter le code de nettoyage ? Si nous transmettons le même *T à de nombreuses fonctions différentes, comment savoir quand il n'est plus accessible et peut être nettoyé ? Comment cela fonctionne-t-il lorsque certaines fonctions doivent continuer à référencer la mémoire après l'appel de la fonction ? Notre approche actuelle est un mélange de :
- durées de vie de l'arène, où la portée du moment où elle est accessible est claire (l'état de l'analyseur n'échappe pas à la fonction appelante et les nœuds AST sont donc un bon choix)
- comptage de références
- faites très attention
De nombreux projets choisissent de répondre à ce genre de questions à travers un guide de style. Le TigerStyle de TigerBeetle est un exemple dans Zig et le guide de style C++ de 31 000 mots de Google en est un autre. Le défi des guides de style est leur application. Comment vous assurez-vous que le guide de style est suivi ? Historiquement, la révision du code était la réponse avec une application au mieux via des linters et des analyseurs statiques.
Avoir un guide de style rigide avec des attentes claires en matière de propriété explicitement énoncées dans le système de types était une véritable option pour Bun. Puisque Zig n'a pas de surcharge d'opérateurs, nous nous retrouverions probablement avec beaucoup de code ressemblant à ceci :
fn foo(a_ptr: SharedPtr(TCPSocket)) !void {
const a: *TCPSocket = a_ptr.get();
defer a_ptr.deref();
const b = try do_something_with_a(a);
defer b.deref();
// ...
}
C'est moins ergonomique que le Zig auquel on s'attend :
fn foo(a: *TCPSocket) !void {
const b = try do_something_with_a(a);
// ...
}
Et le C/C++ ?
Environ 20 % du code de Bun est écrit en C++ et Bun intègre plusieurs bibliothèques C/C++ :
- JavaScriptCore, le moteur JavaScript qui alimente Safari
- uWebSockets et usockets – notre serveur HTTP/WebSocket et notre boucle d'événements
- lshpack & lsquic -
HPACKet bibliothèques HTTP/3 - BoringSSL, le fork OpenSSL de Google
- SQLite
C++ au lieu de Zig serait un choix raisonnable pour Bun. Nous aurions des constructeurs et des destructeurs. Nous pourrions supprimer beaucoup de code wrapper extern "C".
Mais nous dépendrions toujours des guides de style appliqués via la révision du code, et même avec ASAN, une corruption de la mémoire et des fuites de mémoire se produiraient toujours.
Pourquoi Rust?
Un grand pourcentage de bogues de cette liste sont utilisés après libération, double libération et « oubli de libération » dans un chemin d'erreur. En sécurité Rust, il s'agit d'erreurs du compilateur et d'un nettoyage automatique de type RAII avec Drop. Les erreurs du compilateur constituent une meilleure boucle de rétroaction qu'un guide de style.
Historiquement, les réécritures sont une très mauvaise idée. Hors commentaires, Bun correspond à 535 496 lignes de Zig. Une réécriture dans une autre langue prendrait une année complète à une petite équipe d’ingénieurs. Cela signifierait geler les corrections de bogues, les correctifs de sécurité ou le développement de fonctionnalités pour cette période. L'approche la moins risquée pour rendre quelque chose livrable serait un portage mécanique de Zig à Rust, avec un nombre minimal de changements de comportement, en utilisant exactement la même suite de tests que celle que nous utilisons déjà pour tester Bun.
Heureusement, la propre suite de tests de Bun est écrite en TypeScript, ce qui signifie qu'elle ne dépend pas du langage de programmation du runtime.
Une année sans impact sur les utilisateurs n'est pas une option réaliste que nous pourrions envisager. Ainsi, l'application via le style de code pour résoudre les problèmes de stabilité était notre meilleur pari, et c'était notre plan lorsque nous avons ajouté des [pointeurs intelligents] (https://github.com/oven-sh/bun/blob/3a79bd746b11601c9db970b608c73f0b9f96ac81/src/ptr/shared.zig#L569) inspirés des Rust à la base de code de Bun.
Mais honnêtement, je ne voulais pas le faire. Les pointeurs intelligents locaux offrent une moins bonne ergonomie que Rust, sans aucune des garanties.
Et si, à la place, je passais une semaine à tester si le nouveau modèle de Anthropic peut réécrire Bun en Rust ?
Au début, je ne m'attendais pas à ce que ça marche. Quelques jours plus tard, un pourcentage élevé de la suite de tests a commencé à réussir et j'ai vu à quel point le nouveau code Rust correspondait à la base de code Zig d'origine. Mon opinion est passée de "ça vaut la peine d'essayer" à "je vais fusionner ça".
Claude, réécris Bun en Rust.
Il existe de nombreuses façons de faire un travail épouvantable. Par exemple, demander à Claude « Réécrivez Bun en Rust. Ne faites aucune erreur. » et puis prier pour que cela fonctionne n'est pas ce que j'ai fait.
Pensez à la façon dont une personne ferait cela. La première grande question est :
Réécriture incrémentielle ? Ou tout d'un coup ?
D'après mon expérience de portage du transpileur d'esbuild de Go vers Zig pour la version initiale de Bun (sans LLM), tout d'un coup est mieux. Une réécriture incrémentielle ajoute du code temporaire qui, vous l'espérez, sera éventuellement supprimé, et qui serait pénible à court et moyen terme.
La deuxième grande question : comment ?
Comment pouvons-nous conserver Bun dans Rust le même Bun qu'avant, avec la même architecture, les mêmes performances et le même ensemble de fonctionnalités, tout en obtenant également les fonctionnalités linguistiques de Rust comme le vérificateur d'emprunt ? Comment pouvons-nous nous assurer que l'équipe peut toujours le maintenir après la réécriture ?
Faites la réécriture qui semble avoir transpilé notre code Zig en Rust. Nous pouvons le refactoriser progressivement pour réduire l'utilisation de unsafe et ressembler davantage à un Rust idiomatique après la livraison de Bun v1.4.
Ce sont les deux seules grandes questions. Tout le reste n'est que tactique.
Boucles qui écrivent et révisent le code
Une grande partie du travail d'ingénierie quotidien des ingénieurs logiciels peut être simplifié à l'excès en boucles.
// Pseudocode, not real code:
let task;
while ((task = todoList.pop())) {
const result = task();
const feedback = await Promise.all([review(result), review(result)]);
await apply(feedback, result);
}
Un task est associé à un contexte (un ticket Jira, un problème GitHub, etc.). Le result est le code que vous avez écrit pour le corriger. Réviseur(s) de code review les modifications pour vérifier les régressions et l'exactitude. Et puis vous répondez aux commentaires.
J'ai réécrit Bun en Rust en utilisant environ 50 workflows dynamiques en Claude Code exécutés en continu sur une période de 11 jours.
Chaque workflow dynamique était une boucle comme celle-ci : un workflow pour :
- Générer un guide de portage mappant les modèles et types Zig aux modèles et types Rust
- Porter mécaniquement chaque fichier
.zigvers un fichier.rs, faisant correspondre PORTING.md et LIFETIMES.tsv - Corriger les erreurs du compilateur de chaque caisse
- Faire fonctionner des sous-commandes telles que
bun testoubun build - Réussir tous les tests de la suite de tests complète de Bun
- Plusieurs refactors importants et passes de nettoyage
Pendant la majeure partie de ces 11 jours (et au-delà), j'ai surveillé les flux de travail : en lisant manuellement les résultats pour vérifier les problèmes et les bugs, et en invitant Claude à modifier la boucle pour corriger les problèmes.
Comment réviser un PR avec +1 million de lignes ajoutées ? Comment commencer à renforcer la confiance nécessaire pour fusionner de manière responsable de grandes quantités de code créé par LLM ?
Une suite de tests indépendante du langage avec un million d'assertions, une révision contradictoire du code et, en cas de problème, la correction du processus qui génère le code au lieu de le corriger manuellement.
Examen contradictoire
L'examen contradictoire demande à Claude (dans une fenêtre contextuelle distincte) de fournir de manière exhaustive les raisons pour lesquelles les modifications créent des bogues ou ne fonctionnent pas.
Fenêtres contextuelles divisées
Habituellement, avec les humains, la personne qui révise le code n'est pas celle qui l'a écrit. La personne qui écrit le code souhaite le fusionner, ce qui peut inciter ses actions à être expédiées avant qu'il ne soit prêt.
Claude est pareil. Le Claude qui a écrit le code veut que le code soit accepté. Le Claude qui révise veut trouver des problèmes dans le code.
1 implémenteur, 2 examinateurs contradictoires ou plus par implémenteur. Le seul travail du réviseur : trouver les bugs et les raisons pour lesquelles le code ne fonctionne pas. Le responsable de la mise en œuvre n'examine pas. Le réviseur n'implémente pas.
bug 1 sur 3 · la fermeture asynchrone
son contexte : l'original .zig, le plan du port, son propre raisonnement
son contexte : seulement le diff. dit de supposer que le code est faux.
src/runtime/api/bun/js_bun_spawn_bindings.rs · compile proprement
pour stdio dans [spawned_stdout, spawned_stderr] {
correspondre à stdio {
StdioResult::Buffer(mut pipe) => {
// pipe : Boxuv::Pipe – remettez-le à libuv pour qu'il ferme
pipe.close(Subprocess::on_pipe_close)
}
StdioResult::Fd(fd) => fd.close(),
StdioResult::Indisponible => {}
}
}
uv_close est asynchrone : libuv conserve le pointeur de handle brut jusqu'au prochain tick de boucle, puis appelle on_pipe_close, ce qui libère l'allocation. Mais `pipe` est une boîte qui tombe à la fin de ce bras de correspondance - libuv conserve la mémoire libérée, et le rappel de fermeture la libère ensuite une seconde fois. Utilisation après libération, puis double libération.
Box::leak(pipe).close(Subprocess::on_pipe_close)
f0a454376c7 · win-review : js_bun_spawn_bindings.rs fuit Boxuv::Pipe avant async uv_close pour éviter UAF/double-free dans on_pipe_close
Trois bugs que les évaluateurs contradictoires ont réellement détectés : chaque commit cité porte son attribution d'avis dans la ligne d'objet. Tous les trois compilés ; les trois semblaient plausibles. Le réviseur est un deuxième Claude dans sa propre fenêtre contextuelle : il obtient la différence et rien d'autre - aucun raisonnement de l'implémenteur - et il lui est demandé de trouver en quoi il est faux. Le code est condensé des commits cités ; mêmes bugs, mêmes correctifs.
À quoi ça ressemble ?
Si vous êtes sur le point de réaliser quelque chose d'important et coûteux, vous gagnerez du temps et de l'argent en réduisant d'abord les risques.
Travail de préparation
Avant d'écrire du code, j'ai passé environ 3 heures à discuter avec Claude de la façon de mapper étroitement les modèles de notre base de code Zig à Rust. Claude a sérialisé cette discussion dans un document PORTING.md, qui s'est retrouvé sur Hacker News.
La question suivante : comment ajouter des durées de vie Rust au code qui gère manuellement la mémoire ?
C'est là que j'ai demandé à Claude quelque chose comme ceci :
Moi : Lançons un workflow dynamique pour analyser les durées de vie appropriées de chaque champ de structure dans la base de code. Ce flux de travail doit lire chaque champ de structure dans chaque fichier et tracer le flux de contrôle. Tout d'abord, recherchez les champs de structure avec des durées de vie complexes à express dans Rust, puis proposez une durée de vie pour ce champ, puis utilisez 2 agents d'examen contradictoires pour examiner cette durée de vie, puis appliquez les commentaires et sérialisez-les dans un LIFETIMES.tsv pour que d'autres clauses puissent les examiner.
Ensuite, une série d'examens contradictoires sur le PORTING.md et le LIFETIMES.tsv ensemble pour corriger les suggestions contradictoires et tout vérifier. Je l'ai également relu manuellement.
Essai
Avant de demander à Claude de traduire les 1 448 fichiers .zig en fichiers .rs, j'ai commencé avec seulement 3. Pour chacun des 3 fichiers, 1 implémenteur a écrit le nouveau fichier .rs, 2 réviseurs contradictoires ont vérifié que le fichier .rs correspondait au comportement du fichier .zig et qu'il suivait le PORTING.md & LIFETIMES.tsv. Après cela, 1 fixateur a appliqué toutes les suggestions.
Faux départs
J'ai demandé à Claude de boucler le flux de travail sur les 1 448 fichiers .zig, et environ 2 minutes plus tard, un Claude a exécuté git stash avant de valider. Un autre a exécuté git stash pop. Et puis git reset HEAD --hard. Ils se marchaient dessus ! Et si je plaçais chaque Claude dans un arbre de travail distinct, je manquerais d'espace disque car le référentiel git de Bun est trop volumineux et les modifications devront finalement être compilées et vues ensemble.
J'ai donc demandé à Claude de modifier le flux de travail pour lui demander de ne jamais exécuter git stash ou git reset ou toute commande git qui ne valide pas un fichier spécifique à la fois. Non cargo non plus. Aucune commande lente du tout.
Ensuite, Claude a repris les workflows. Et ça fonctionnait ! Trop lentement, je l'ai donc divisé en seulement 4 fragments de flux de travail, chacun avec son propre arbre de travail (4 arbres de travail au total), chacun exécutant 16 claudes validant et poussant des fichiers.
Enfin écrire le code
Grâce à toute la parallélisation et à ce travail de préparation, Claude écrivait au maximum environ 1 300 lignes de code par minute. Chaque ligne de code a été examinée par deux réviseurs contradictoires distincts (également Claude) et a fait l'objet d'une série de correctifs avant d'être validée. Absolument rien de tout cela n'a encore fonctionné.
11 jours × 24 heures · PDT
5 463 commits
1 695 commits/heure
00h06h12h18h4 mai4 mai, 7h00-8h00 PDT — 6 commits, +89 278 lignes4 mai, 8h00-9h00 PDT — 2 commits, +50 742 lignes4 mai, 9h00-10h00 PDT — 1 commit, +28 149 lignes4 mai, 11h00-12h00 PDT — 1 commit, +39 752 lignes4 mai, de 12h à 13h PDT — 3 commits, +251 616 lignes4 mai, de 13h à 14h PDT — 2 commits, +161 724 lignes4 mai, de 15h à 16h PDT — 3 commits, +136 381 lignes4 mai, de 17h à 18h PDT — 5 commits, +895 lignes4 mai 18h-19h PDT — 5 commits, +17 027 lignes4 mai, 19h-20h PDT — 1 commit, +106 lignes4 mai, 21h-22h PDT — 13 commits, +11 661 lignes4 mai, 23h-00h PDT — 6 commits, +8 516 lignes5 mai 5 mai, 00h-1h PDT — 9 commits, +1 381 lignes5 mai, 1h-2h PDT — 7 commits, +1 577 lignes5 mai, 2h-3h PDT — 4 commits, +2 035 lignes5 mai, 3h-4h PDT — 4 commits, +7 808 lignes5 mai, 4h-5h PDT — 1 commit, +2 796 lignes5 mai 5h00 à 6h00 PDT — 2 commits, +29 370 lignes5 mai, 8h00 à 9h00 PDT — 2 commits, +7 076 lignes5 mai, 9h00 à 10h00 PDT — 2 commits, +308 lignes5 mai, 11h00 à 12h00 PDT — 2 commits, +1 643 lignes5 mai, 12h00 à 13h00 PDT — 4 commits, +1 452 lignes5 mai, de 13h à 14h PDT — 1 commit, +2 142 lignes5 mai, de 14h à 15h PDT — 4 commits, +7 787 lignes5 mai, de 15h à 16h PDT — 2 commits, +5 835 lignes5 mai, de 16h à 17h PDT — 1 commit, +3 417 lignes5 mai, de 17h à 18h PDT — 4 commits, +3 960 lignes5 mai, de 18 h à 19 h PDT — 4 commits, +9 179 lignes5 mai, de 19 h à 20 h PDT — 4 commits, +1 983 lignes5 mai, de 20 h à 21 h PDT — 4 commits, +18 902 lignes5 mai, de 21 h à 22 h PDT — 43 commits, +40 650 lignesMai 5 mai, 22h-23h PDT — 139 commits, +64 842 lignes5 mai, 23h-00h PDT — 141 commits, +34 814 lignes6 mai6 mai, 00h-1h PDT — 60 commits, +10 417 lignes6 mai, 1h-2h PDT — 296 commits, +38 530 lignes6 mai, 2h-3h PDT — 306 commits, +18 836 lignes6 mai, 3h-4h PDT — 196 commits, +10 245 lignes6 mai, 4h-5h PDT — 86 commits, +2 655 lignes6 mai, 5h-6h PDT — 16 commits, +289 lignes6 mai, 8h-9h PDT — 5 commits, +264 lignes6 mai, de 9h à 10h PDT — 458 commits, +16 409 lignes6 mai, de 10h à 11h PDT — 695 commits, +44 000 lignes6 mai, de 11h à 12h PDT — 102 commits, +21 972 lignes6 mai, de 12h à 13h PDT — 19 commits, +2 891 lignes6 mai, 13h-14h PDT — 3 commits, +56 lignes6 mai, 15h-16h PDT — 64 commits, +3 606 lignes6 mai, 16h-17h PDT — 264 commits, +60 132 lignes6 mai, 17h-18h PDT — 268 commits, +40 953 lignesMai 6 mai, de 18 h à 19 h PDT — 281 commits, +16 283 lignes6 mai, de 19 h à 20 h PDT — 258 commits, +26 654 lignes6 mai, de 20 h à 21 h PDT — 327 commits, +16 599 lignes6 mai, de 21 h à 22 h PDT — 74 commits, +8 331 lignes6 mai 22h-23h PDT — 17 commits, +2 200 lignes6 mai, 23h-00h PDT — 11 commits, +3 590 lignes7 mai 7 mai, 00h-1h PDT — 17 commits, +6 577 lignes7 mai, 1h-2h PDT — 22 commits, +8 718 lignes7 mai, 2h-3h PDT — 21 commits, +11 392 lignes7 mai, 3 h 00 à 4 h 00 PDT — 53 commits, +6 476 lignes7 mai, 4 h 00 à 5 h 00 PDT — 31 commits, +2 356 lignes7 mai, 5 h 00 à 6 h 00 PDT — 9 commits, +1 787 lignes7 mai, 6 h 00 à 7 h 00 PDT — 4 commits, +580 lignesMai 7 mai, de 7 h à 8 h PDT — 5 commits, +181 lignes7 mai, de 11 h à 12 h PDT — 3 commits, +421 lignes7 mai, de 12 h à 13 h PDT — 1 commit, +13 lignes7 mai, de 13 h à 14 h PDT — 5 commits, +248 lignes7 mai, de 14 h à 15 h PDT — 9 commits, +2 131 lignesMai 7 mai, de 15 h à 16 h PDT — 51 commits, +3 207 lignes7 mai, de 16 h à 17 h PDT — 56 commits, +2 647 lignes7 mai, de 17 h à 18 h PDT — 159 commits, +2 787 lignes7 mai, de 18 h à 19 h PDT — 42 commits, +1 590 lignes7 mai, de 19 h à 20 h PDT — 46 commits, +4 170 lignes7 mai, 20 h 00 à 21 h 00 PDT — 52 commits, +2 113 lignes7 mai, 21 h 00 à 22 h 00 PDT — 27 commits, +1 585 lignes7 mai, 22 h 00 à 23 h 00 PDT — 27 commits, +2 231 lignes7 mai, 23 h 00 à minuit PDT — 30 commits, +4 987 lignes8 mai8 mai, de 00h00 à 1h00 PDT — 27 commits, +1 196 lignes8 mai, de 1h00 à 2h00 PDT — 14 commits, +904 lignes8 mai, de 2h00 à 3h00 PDT — 8 commits, +536 lignes8 mai, de 3h00 à 4h00 PDT — 13 commits, +253 lignes8 mai, de 4h00 à 5h00 PDT — 3 commits, +771 lignes8 mai, 5h00-6h00 PDT — 15 commits, +1 545 lignes8 mai, 6h00-7h00 PDT — 12 commits, +1 965 lignes8 mai, 7h00-8h00 PDT — 14 commits, +1 866 lignes8 mai, 8h00-9h00 PDT — 55 commits, +3 622 lignesMai 8 mai, de 9 h à 10 h PDT — 35 commits, +4 778 lignes8 mai, de 10 h à 11 h PDT — 1 commit, +0 lignes8 mai, de 12 h à 13 h PDT — 1 commit, +116 lignes8 mai, de 13 h à 14 h PDT — 2 commits, +66 lignes8 mai, de 14 h à 15 h PDT — 9 commits, +1 071 lignes8 mai, de 15h à 16h PDT — 26 commits, +1 691 lignes8 mai, de 16h à 17h PDT — 18 commits, +2 751 lignes8 mai, de 17h à 18h PDT — 2 commits, +97 lignes8 mai, de 18h à 19h PDT — 2 commits, +135 lignes8 mai, de 19h à 20h PDT — 11 commits, +1 763 lignes8 mai, 20h-21h PDT — 20 commits, +5 272 lignes8 mai, 21h-22h PDT — 12 commits, +952 lignes8 mai, 22h-23h PDT — 2 commits, +334 lignes8 mai, 23h-00h PDT — 6 commits, +2 033 lignes9 mai 9 mai 00h00 à 1h00 PDT — 9 commits, +387 lignes9 mai, 1h00 à 2h00 PDT — 9 commits, +723 lignes9 mai, 2h00 à 3h00 PDT — 8 commits, +98 lignes9 mai, 3h00 à 4h00 PDT — 63 commits, +2 538 lignes9 mai, 4h00 à 5h00 PDT — 11 commits, +8 861 lignesMai 9 mai, de 5 h à 6 h PDT — 4 commits, +42 lignes9 mai, de 6 h à 7 h PDT — 3 commits, +2 616 lignes9 mai, de 7 h à 8 h PDT — 6 commits, +6 993 lignes9 mai, de 8 h à 9 h PDT — 1 commit, +3 705 lignes9 mai, de 9 h à 10 h PDT — 11 commits, +199 lignes9 mai, de 11h à 12h PDT — 1 commit, +23 lignes9 mai, de 12h à 13h PDT — 4 commits, +5 012 lignes9 mai, de 13h à 14h PDT — 7 commits, +2 080 lignes9 mai, de 14h à 15h PDT — 6 commits, +924 lignes9 mai, de 15h à 16h PDT — 5 commits, +248 lignes9 mai, 16h-17h PDT — 17 commits, +508 lignes9 mai, 17h-18h PDT — 2 commits, +135 lignes9 mai, 18h-19h PDT — 4 commits, +822 lignes9 mai, 19h-20h PDT — 1 commit, +7 lignes10 mai 10 mai, 00h-1h PDT — 4 commits, +497 lignes10 mai, 1h-2h PDT — 2 commits, +35 lignes10 mai, 2h-3h PDT — 1 commit, +131 lignes10 mai, 3h-4h PDT — 2 commits, +322 lignes10 mai, 4h-5h PDT — 1 commit, +3 lignes10 mai, 5h-6h PDT — 1 commit, +26 lignes10 mai, 6h-7h PDT — 2 commits, +81 lignes10 mai, 7h-8h PDT — 1 commit, +5 lignes10 mai, 8h-9h PDT — 4 commits, +78 lignes10 mai, 9h-10h PDT — 1 commit, +1 lignes10 mai, 10h-11h PDT — 2 commits, +128 lignes10 mai, de 11h à 12h PDT — 1 commit, +4 lignes10 mai, de 12h à 13h PDT — 2 commits, +413 lignes10 mai, de 13h à 14h PDT — 1 commit, +25 lignes10 mai, de 14h à 15h PDT — 5 commits, +327 lignes10 mai, de 15h à 16h PDT — 6 commits, +1 172 lignes10 mai, 16h-17h PDT — 4 commits, +752 lignes10 mai, 17h-18h PDT — 3 commits, +227 lignes10 mai, 18h-19h PDT — 2 commits, +242 lignes10 mai, 19h-20h PDT — 1 commit, +306 lignes10 mai, 20h-21h PDT — 1 commit, +54 lignes10 mai, 21h-22h PDT — 2 commits, +75 lignes10 mai, 22h-23h PDT — 1 commit, +134 lignes10 mai, 23h-00h PDT — 5 commits, +103 lignes11 mai11 mai, 00h-1h PDT — 2 commits, +150 lignes11 mai 1h-2h PDT — 4 commits, +398 lignes11 mai, 2h-3h PDT — 2 commits, +364 lignes11 mai, 3h-4h PDT — 3 commits, +44 lignes11 mai, 4h-5h PDT — 7 commits, +9 367 lignes11 mai, 6h-7h PDT — 2 commits, +43 lignesMai 11 mai, de 7 h à 8 h PDT — 2 commits, +149 lignes11 mai, de 8 h à 9 h PDT — 10 commits, +2 171 lignes11 mai, de 9 h à 10 h PDT — 16 commits, +2 047 lignes11 mai, de 10 h à 11 h PDT — 18 commits, +3 356 lignes11 mai 11h00-12h00 PDT — 9 commits, +861 lignes11 mai, 12h00-13h00 PDT — 3 commits, +412 lignes11 mai, 13h00-14h00 PDT — 12 commits, +2 978 lignes11 mai, 14h00-15h00 PDT — 157 commits, +10 700 lignes11 mai, 15h00-16h00 PDT — 16 commits, +1 346 lignes11 mai, 16h-17h PDT — 3 commits, +78 lignes11 mai, 17h-18h PDT — 41 commits, +2 568 lignes11 mai, 18h-19h PDT — 55 commits, +4 912 lignes11 mai, 19h-20h PDT — 53 commits, +3 475 lignesMai 11 mai, de 20 h à 21 h PDT — 32 commits, +1 732 lignes11 mai, de 21 h à 22 h PDT — 46 commits, +4 506 lignes11 mai, de 22 h à 23 h PDT — 45 commits, +1 711 lignes11 mai, de 23 h à minuit PDT — 52 commits, +10 850 lignes12 mai 12, 00h00-1h00 PDT — 30 commits, +3 760 lignes12 mai, 1h00-2h00 PDT — 24 commits, +9 443 lignes12 mai, 2h00-3h00 PDT — 41 commits, +1 635 lignes12 mai, 3h00-4h00 PDT — 39 commits, +788 lignes12 mai, 4h00-5h00 PDT — 27 commits, +651 lignes12 mai, 5h00-6h00 PDT — 23 commits, +779 lignes12 mai, 6h00-7h00 PDT — 1 commit, +137 576 lignes12 mai, 7h00-8h00 PDT — 2 commits, +81 lignes12 mai, 8h00-9h00 PDT — 2 commits, +75 lignes12 mai 9h-10h PDT — 2 commits, +130 lignes12 mai, 10h-11h PDT — 5 commits, +160 lignes12 mai, 11h-12h PDT — 2 commits, +20 lignes12 mai, 12h-13h PDT — 1 commit, +2 lignes12 mai, 13h-14h PDT — 30 commits, +2 677 lignes12 mai, de 14h à 15h PDT — 41 commits, +7 022 lignes12 mai, de 15h à 16h PDT — 4 commits, +200 lignes12 mai, de 16h à 17h PDT — 27 commits, +1 423 lignes12 mai, de 17h à 18h PDT — 19 commits, +1 055 lignes12 mai 18h00-19h00 PDT — 2 commits, +380 lignes12 mai, 19h00-20h00 PDT — 2 commits, +84 lignes12 mai, 21h00-22h00 PDT — 7 commits, +273 lignes12 mai, 22h00-23h00 PDT — 3 commits, +230 lignes12 mai, 23h00-00h00 PDT — 7 commits, +319 lignes13 mai13 mai, de 00h00 à 1h00 PDT — 2 commits, +133 lignes13 mai, de 1h00 à 2h00 PDT — 14 commits, +2 177 lignes13 mai, de 2h00 à 3h00 PDT — 12 commits, +685 lignes13 mai, de 4h00 à 5h00 PDT — 10 commits, +657 lignes13 mai, de 5h00 à 6h00 PDT – 1 commit, +687 lignes13 mai, 6h-7h PDT – 11 commits, +380 lignes13 mai, 7h-8h PDT – 12 commits, +5 247 lignes13 mai, 8h-9h PDT – 14 commits, +1 051 lignes13 mai, 9h-10h PDT – 7 commits, +680 lignesMai 13 mai, 10h-11h PDT — 10 commits, +412 lignes13 mai, 11h-12h PDT — 6 commits, +314 lignes13 mai, 12h-13h PDT — 10 commits, +2 980 lignes13 mai, 13h-14h PDT — 1 commit, +0 lignes13 mai, 14h-15h PDT — 3 commits, +439 lignes13 mai, de 17h à 18h PDT — 7 commits, +114 lignes13 mai, de 18h à 19h PDT — 4 commits, +605 lignes13 mai, de 21h à 22h PDT — 1 commit, +13 lignes13 mai, de 22h à 23h PDT — 1 commit, +48 lignes13 mai, de 23h à 00h PDT — 1 commit, +8 lignes14 mai14 mai, de 00h00 à 1h00 PDT — 1 commit, +150 lignes
Chaque commit sur la branche du port (fusions exclues), regroupé par heure. Heure de pointe : 695 commits.
Vous avez remarqué un timing incohérent ? J'ai oublié d'augmenter les IOPS par défaut sur l'instance EC2 sur laquelle elle s'est exécutée. Une seule commande grep lente a suffi à geler les lectures et écritures sur le disque pendant quelques minutes.
Erreurs du compilateur en tant que file d'attente de travail
Après avoir écrit tout le code, j'ai demandé à Claude d'écrire un workflow corrigeant chaque erreur du compilateur. Nous y sommes allés caisse par caisse.
≈15 125 erreurs restantes
Mercredi 6 mai, 01h29 PDT
erreurs.txt88 correctifs validés
erreur : src/event_loop/SpawnSyncEventLoop.rs
erreur : src/sys/lib.rs
erreur : runtime/timer/TimerObjectInternals.rs
erreur : src/runtime/ffi/ffi_body.rs
erreur : src/http/lib.rs
erreur : src/js_parser/ast/Parser.rs
erreur : JSPromise.rs
erreur : autonome_graph/StandaloneModuleGraph.rs
erreur : src/http_jsc/websocket_client.rs
erreur : doStep5.rs
erreur : src/install/PackageManager.rs
divisé · 64 claudes
arbre de travail 1
→→
→→
→→
→→
arbre de travail 2
→→
→→
→→
→→
arbre de travail 3
→→
→→
→→
→→
arbre de travail 4
→→
→→
→→
→→
1 correctif2 avis1 appliqué
→ engage un terrain par caisse
chignon_bundler0
chignon_js_parser17
chignon_css10
chignon_http3
chignon_sys3
chignon_alloc1
bun_sourcemap3
chignon_ini0
bun_analytics1
chignon_zlib0
· phase-d(sql_jsc/mysql/protocol) : correction des importations, limites ReaderContext, assistant WTFStringImpl
· phase-d(tier0) : bun_sourcemap parse_json : champ BabyList.len, Option<StoreRef> déballage
· phase-d(jpa) : skipTypescript.rs — portage des corps réels, suppression des stubs _draft/todo
Comment fonctionnait la phase D, rejouée à partir de ses 1 610 commits réels (6 mai, PDT) : cargo check a écrit ≈16 000 erreurs dans un fichier, regroupées par caisse ; le flux de travail les a répartis entre 64 Claudes - 16 boucles sur 4 arbres de travail, chacun corrigeant Claude, deux révisant, un appliquant. Chaque jeton est un lot de commits réels : il atterrit sur sa caisse réelle et ce n'est qu'alors que les compteurs bougent. Les lignes d'erreur sont de véritables sujets de commit.
La classe d'erreur la plus délicate était celle des dépendances cycliques.
Notre base de code Zig était une unité de compilation (en fait une caisse). Je voulais diviser la nouvelle base de code Rust en ~100 caisses afin que le Rust se compile plus rapidement, mais cela devait éviter les dépendances cycliques tout en minimisant les changements par rapport à l'implémentation Zig d'origine. Mon PR pour le faire immédiatement avant de commencer la réécriture Rust était insuffisant. Au lieu de recommencer, j'ai exécuté un autre workflow pour classer où le code avec des dépendances cycliques devait aller et tout écrire - puis un autre workflow pour effectuer le refactor.
La correction des dépendances cycliques a révélé environ 16 000 erreurs du compilateur. Un nombre énorme pour 1 humain, mais pas un nombre fou pour 64 claudes à la fois.
Pour maximiser le parallélisme, le flux de travail a été effectué en boucle sur chaque caisse.
- Pour chaque caisse, exécutez
cargo check, regroupez la sortie par fichier et enregistrez les erreurs dans un fichier - Corrigez toutes les erreurs du compilateur dans cette caisse
- 2 évaluateurs contradictoires pour les modifications de la caisse
- 1 fixateur applique les correctifs
Pour éviter que Claude ne se marche dessus, cargo check n'a couru qu'au tout début et comme les autres courses, pas de git jusqu'à la fin.
Encore un faux départ
Claude a interprété "comstackons toutes les caisses" comme "éliminons les fonctions avec des erreurs de compilation". Claude a également commencé à ajouter des commentaires explicatifs étrangement longs pour documenter les solutions de contournement. J'ai donc ajouté cette règle que les réviseurs contradictoires doivent rejeter :
Si vous avez besoin d'un commentaire d'un paragraphe pour justifier pourquoi la solution de contournement est correcte, le code est erroné : corrigez le code.
Une seule modification et quelques heures plus tard, ces choses ont cessé de se produire.
Tests de fumée
Les mannequins adorent dire "tests de fumée"
Une fois cargo check passé, il fallait ensuite le compiler et l'exécuter bun --version. Il y avait des erreurs de liaison. Ensuite, il a paniqué immédiatement au démarrage.
L'objectif suivant était de le faire fonctionner bun test <file>. Une fois que cela a fonctionné, nous avons pu commencer à faire des tests ! Il est temps de passer à un autre workflow, en boucle sur les sous-commandes CLI bun :
- Enregistrer chaque trace de pile défaillante dans un fichier avec sa sous-commande
- Pour chaque stacktrace défaillant regroupé par sous-commande, ayez 1 correctif Claude
- 2 évaluateurs contradictoires
- 1 fixateur applique les suggestions
Obtenir la suite de tests réussie localement
Ce workflow a bouclé sur les fichiers de test.
Exécutez environ 100 fichiers de test aléatoires répartis dans l'un des 4 arbres de travail par dossier dans la base de code. Pour chaque test échoué, enregistrez la trace de la pile et les erreurs dans un fichier, 1 implémenteur propose un correctif, 2 réviseurs contradictoires, puis 1 correcteur s'applique.
Encore plus de faux départs
Notre suite de tests comporte de nombreux tests de fuite de mémoire et une poignée de tests d'intégration qui peuvent prendre plus d'une minute. Par exemple : un test qui s'exécute next dev et vérifie le rechargement à chaud du module peut détecter les modifications 100 fois. Plusieurs de ces tests expirent dans les versions de débogage.
Nous avons également des tests de stress qui épuisent le nombre maximum de sockets TCP sur la machine, des tests qui lisent et écrivent des gigaoctets sur le disque et des tests qui génèrent ~10 000 processus.
Cela nécessitait une isolation plus forte que "s'il vous plaît", nous avons donc utilisé systemd-run (cgroups) pour limiter l'utilisation de la mémoire et du processeur et isoler les espaces de noms pid. La machine a manqué d'espace disque et a quand même planté plusieurs fois.
Obtenir la réussite de la suite de tests dans CI
Deux jours après la première exécution de CI, la liste des fichiers de test défaillants était passée de 972 à 23. Un jour et demi plus tard, Linux est passé au vert — et pour la première fois, il semblait que cette Rust réécriture allait réellement fonctionner.
0/6 plateformes vertes
version n° 53047 · samedi 9 mai, 11h52 PDT
macOS x64 · 2 fragments
build #52897 : échecs de fragments, build #52932 : échecs de fragments, build #52934 : échecs de fragments, build #52938 : échecs de fragments, build #52944 : échecs de fragments, build #52946 : échecs de fragments, build #52949 : échecs de fragments, build #52975 : échecs de fragments, build #52998 : échecs de fragmentsbuild #53007 : échecs de fragmentsbuild #53015 : échecs de fragmentsbuild #53026 : échecs de fragmentsbuild #53027 : échecs de fragmentsbuild #53035 : échecs de fragmentsbuild #53041 : échecs de fragmentsbuild #53047 : échecs de fragmentsbuild #53056 : échecs de fragmentsbuild #53077 : échecs de fragmentsbuild #53090 : échecs de fragmentsbuild #53095 : échecs de fragmentsbuild #53106 : échecs de fragmentsbuild #53109 : échecs de fragmentsbuild #53123 : échecs de fragmentsbuild #53127 : échecs de fragmentsbuild #53130 : échecs de fragmentsbuild #53131 : échecs de fragmentsbuild #53133 : échecs de fragmentsbuild #53134 : échecs de fragmentsbuild #53143 : échecs de fragmentsbuild #53149 : tous les fragments ont réussibuild #53159 : tous les fragments ont réussibuild #53164 : tous les fragments ont réussibuild #53167 : tous les fragments ont réussibuild #53172 : tous les fragments ont réussibuild #53176 : tous les fragments ont réussibuild #53194 : tous les fragments ont réussi la build #53208 : tous les fragments ont réussi la build #53213 : échecs de partition build #53214 : échecs de partition build #53216 : aucun échec (exécution partielle) build #53222 : tous les fragments ont réussi la build #53229 : aucun échec (exécution partielle) build #53241 : tous les fragments ont réussi la build #53265 : tous les fragments sont passésbuild #53271 : tous les fragments ont été réussisbuild #53304 : échecs de fragmentsbuild #53327 : échecs de fragmentsbuild #53340 : échecs de fragmentsbuild #53401 : échecs de fragmentsbuild #53431 : échecs de fragmentsbuild #53491 : tous les fragments sont passésbuild #53503 : tous fragments passésbuild #53748 : échecs de fragmentsbuild #53753 : tous les fragments passésbuild #53787 : tous les fragments passésbuild #53811 : tous les fragments ont réussibuild #53933 : aucun échec (exécution partielle)build #53952 : tous les fragments ont réussibuild #53983 : échecs de fragmentsbuild #53992 : échecs de fragmentsbuild #53999 : échecs de fragments, build #54012 : tous les fragments ont réussi, build #54015 : tous les fragments ont réussi, build #54017 : tous les fragments ont réussi, build #54022 : échecs de fragments, build #54026 : échecs de fragments, build #54033 : tous les fragments ont réussi, build #54040 : tous les fragments ont réussi, build #54047 : échecs de fragmentsbuild #54049 : échecs de fragmentsbuild #54057 : échecs de fragmentsbuild #54064 : tous les fragments réussisbuild #54074 : tous les fragments réussisbuild #54093 : tous les fragments réussisbuild #54144 : aucun échec (exécution partielle)build #54161 : échecs de fragmentsbuild #54186 : échecs de fragmentsbuild #54189 : échecs de partition de construction #54196 : échecs de partition de construction #54202 : tous les fragments ont réussi
✓
Linux arm64 · 60 fragments
build #52934 : échecs de fragments, build #52938 : échecs de fragments, build #52944 : échecs de fragments, build #52969 : échecs de fragments, build #52975 : échecs de fragments, build #52980 : échecs de fragments, build #52988 : échecs de fragments, build #52996 : échecs de fragments, build #52998 : échecs de partition build # 53007 : échecs de partition build # 53013 : échecs de partition build # 53014 : échecs de partition build # 53015 : échecs de partition build # 53026 : échecs de partition build # 53027 : échecs de partition build # 53031 : aucun échec (exécution partielle) build # 53032 : échecs de partition build #53035 : échecs de partition, build #53041 : échecs de fragment, build #53047 : échecs de fragment, build #53056 : échecs de fragment, build #53059 : échecs de fragment, build #53077 : échecs de fragment, build #53083 : échecs de fragment, build #53086 : échecs de fragment, build #53090 : échecs de fragment, build #53095 : échecs de partition, build #53106 : échecs de fragment, build #53109 : échecs de fragment, build #53123 : échecs de fragment, build #53127 : échecs de fragment, build #53130 : échecs de fragment, build #53131 : échecs de fragment, build #53133 : échecs de fragment, build #53134 : échecs de fragment, build #53135 : échecs de fragments, build #53143 : échecs de fragments, build #53149 : échecs de fragments, build #53159 : échecs de fragments, build #53164 : échecs de fragments, build #53167 : tous les fragments réussis, build #53172 : échecs de fragments, build #53176 : tous les fragments réussis, build #53188 : fragment Failuresbuild #53194 : échecs de fragmentsbuild #53208 : tous les fragments ont réussibuild #53212 : échecs de fragmentsbuild #53213 : échecs de fragmentsbuild #53214 : échecs de fragmentsbuild #53216 : tous les fragments ont réussibuild #53222 : tous les fragments ont réussibuild #53229 : tous les fragments ont réussibuild #53236 : aucun échec (exécution partielle) build #53241 : tous les fragments réussisbuild #53260 : tous les fragments passésbuild #53265 : tous les fragments passésbuild #53271 : tous les fragments passésbuild #53280 : aucun échec (exécution partielle)build #53298 : aucun échec (exécution partielle)build #53304 : échecs de fragmentbuild #53327 : tous fragments passésbuild #53340 : tous les fragments ont réussibuild #53360 : aucun échec (exécution partielle)build #53419 : échecs de fragmentbuild #53431 : échecs de fragmentbuild #53458 : aucun échec (exécution partielle)build #53485 : échecs de fragmentbuild #53491 : échecs de fragmentbuild #53503 : échecs de fragmentbuild # 53514 : aucun échec (exécution partielle) build # 53570 : échecs de partition build # 53583 : échecs de partition build # 53599 : échecs de partition build # 53748 : échecs de partition build # 53753 : tous les fragments ont été transmis build # 53762 : aucun échec (exécution partielle) build # 53787 : aucun échec (exécution partielle) build #53811 : tous les fragments ont réussi build #53852 : aucun échec (exécution partielle) build #53863 : échecs de fragment build #53893 : échecs de fragment build #53914 : tous les fragments ont réussi build #53933 : tous les fragments ont réussi build #53952 : tous les fragments ont réussi build #53983 : échecs de fragment build #53992 : échecs de fragmentsbuild #53999 : échecs de fragmentsbuild #54008 : aucun échec (exécution partielle)build #54012 : tous les fragments passésbuild #54015 : tous les fragments passésbuild #54017 : tous les fragments réussisbuild #54022 : échecs de fragmentsbuild #54026 : tous les fragments passésbuild #54030 : échecs de fragmentsbuild #54033 : tous les fragments ont réussi build #54040 : tous les fragments ont réussibuild #54047 : échecs de fragmentbuild #54049 : échecs de fragmentbuild #54055 : échecs de fragmentbuild #54057 : échecs de fragmentbuild #54064 : tous les fragments ont réussibuild #54074 : tous les fragments ont réussibuild #54083 : aucun échec (exécution partielle)build #54093 : tous les fragments réussisbuild #54144 : tous les fragments réussisbuild #54161 : échecs de fragmentbuild #54186 : échecs de fragmentbuild #54189 : échecs de fragmentbuild #54196 : échecs de fragmentbuild #54202 : tous les fragments réussis
✓
Linux x64 · 60 fragments
build #52934 : échecs de fragments, build #52938 : échecs de fragments, build #52944 : échecs de fragments, build #52969 : échecs de fragments, build #52975 : échecs de fragments, build #52988 : échecs de fragments, build #52996 : échecs de fragments, build #52998 : échecs de fragments, build #53007 : échecs de fragmentsbuild #53013 : échecs de fragmentsbuild #53014 : échecs de fragmentsbuild #53015 : échecs de fragmentsbuild #53026 : échecs de fragmentsbuild #53027 : échecs de fragmentsbuild #53032 : échecs de fragmentsbuild #53033 : échecs de fragmentsbuild #53035 : échecs de fragmentsbuild #53041 : échecs de partition build # 53047 : échecs de partition build # 53056 : échecs de partition build # 53059 : aucun échec (exécution partielle) build # 53077 : échecs de partition build # 53083 : aucun échec (exécution partielle) build # 53086 : échecs de partition build # 53090 : échecs de partition build # 53095 : échecs de partition build #53106 : échecs de partition, build #53109 : échecs de fragment, build #53123 : échecs de fragment, build #53127 : échecs de fragment, build #53130 : échecs de fragment, build #53131 : échecs de fragment, build #53133 : échecs de fragment, build #53134 : échecs de fragment, build #53135 : échecs de fragment, build #53143 : échecs de fragments, build #53149 : échecs de fragments, build #53159 : échecs de fragments, build #53164 : échecs de fragments, build #53167 : tous les fragments ont réussi, build #53172 : tous les fragments ont réussi, build #53176 : tous les fragments ont réussi, build #53188 : échecs de fragments, build #53194 : tous fragments passésbuild #53208 : tous les fragments ont réussibuild #53212 : échecs de fragmentsbuild #53213 : échecs de fragmentsbuild #53214 : échecs de fragmentsbuild #53216 : tous les fragments ont réussibuild #53222 : tous les fragments ont réussibuild #53229 : tous les fragments ont réussibuild #53236 : aucun échec (exécution partielle)build #53241 : tous les fragments ont réussi la build #53260 : tous les fragments ont réussi la build #53265 : tous les fragments ont réussi la build #53271 : tous les fragments ont réussi la build #53280 : aucun échec (exécution partielle) build #53304 : échecs de partition build #53327 : tous les fragments ont réussi la build #53340 : tous les fragments ont réussi la build #53360 : aucun échec (exécution partielle) build #53419 : aucun échec (exécution partielle) build #53431 : échecs de partition build #53458 : aucun échec (exécution partielle) build #53485 : échecs de partition build #53491 : tous les fragments ont réussi build #53503 : tous les fragments ont réussi build #53514 : aucun échec (exécution partielle) build #53570 : échecs de fragments, build #53583 : échecs de fragments, build #53599 : échecs de fragments, build #53748 : échecs de fragments, build #53753 : tous les fragments ont été transmis, build #53759 : aucun échec (exécution partielle) build #53781 : échecs de fragments, build #53787 : aucun échec (exécution partielle) build #53811 : tous les fragments ont réussibuild #53863 : échecs de fragmentsbuild #53893 : échecs de fragmentsbuild #53914 : tous les fragments ont réussibuild #53933 : échecs de fragmentsbuild #53952 : tous les fragments ont réussibuild #53983 : échecs de fragmentsbuild #53992 : échecs de fragmentsbuild #53999 : échecs de fragmentsbuild #54008 : aucun échec (exécution partielle) build #54012 : tous les fragments ont réussi build #54015 : tous les fragments ont réussibuild #54017 : tous les fragments ont réussibuild #54022 : échecs de fragmentsbuild #54026 : tous les fragments ont réussibuild #54030 : échecs de fragmentsbuild #54033 : tous les fragments ont réussibuild #54040 : aucun échec (exécution partielle) build #54047 : échecs de partition build #54049 : échecs de partition build #54055 : échecs de partition build #54057 : échecs de partition build #54064 : tous les fragments ont réussi build #54074 : tous les fragments ont réussi build #54083 : aucun échec (exécution partielle) build #54093 : tous les fragments ont réussi, build #54144 : tous les fragments ont réussi, build #54161 : échecs de fragments, build #54186 : échecs de fragments, build #54189 : échecs de fragments, build #54196 : échecs de fragments, build #54202 : tous les fragments ont réussi
✓
macOS arm64 · 4 fragments
build #52897 : échecs de fragments, build #52929 : échecs de fragments, build #52932 : échecs de fragments, build #52944 : échecs de fragments, build #52975 : échecs de fragments, build #52996 : échecs de fragments, build #52998 : échecs de fragments, build #53007 : échecs de fragments, build #53013 : échecs de fragmentsbuild #53014 : échecs de fragmentsbuild #53015 : échecs de fragmentsbuild #53026 : échecs de fragmentsbuild #53027 : échecs de fragmentsbuild #53032 : échecs de fragmentsbuild #53035 : échecs de fragmentsbuild #53041 : échecs de fragmentsbuild #53047 : échecs de fragmentsbuild #53056 : échecs de fragmentsbuild #53059 : échecs de fragmentsbuild #53077 : échecs de fragmentsbuild #53095 : échecs de fragmentsbuild #53109 : échecs de fragmentsbuild #53123 : échecs de fragmentsbuild #53127 : échecs de fragmentsbuild #53130 : échecs de fragmentsbuild #53131 : échecs de fragmentsbuild #53133 : échecs de fragmentsbuild #53134 : échecs de fragmentsbuild #53135 : échecs de fragmentsbuild #53143 : échecs de fragmentsbuild #53149 : échecs de fragmentsbuild #53159 : échecs de fragmentsbuild #53164 : échecs de fragmentsbuild #53167 : échecs de fragmentsbuild #53172 : échecs de fragmentsbuild #53176 : échecs de fragmentsbuild #53188 : échecs de fragmentsbuild #53194 : échecs de fragmentsbuild #53208 : échecs de fragmentsbuild #53212 : échecs de fragmentsbuild #53213 : échecs de fragmentsbuild #53214 : échecs de fragmentsbuild #53216 : échecs de fragmentsbuild #53222 : échecs de fragmentsbuild #53229 : non échecs (exécution partielle) build # 53236 : aucun échec (exécution partielle) build # 53241 : tous les fragments passés build # 53265 : tous les fragments passés build # 53271 : aucun échec (exécution partielle) build # 53280 : aucun échec (exécution partielle) build # 53304 : échecs de fragment build # 53327 : aucun échec (exécution partielle) build #53340 : aucun échec (exécution partielle) build #53360 : aucun échec (exécution partielle) build #53368 : échecs de partition build #53379 : échecs de partitionbuild #53383 : échecs de partitionbuild #53401 : échecs de partitionbuild #53431 : échecs de partitionbuild #53458 : échecs de partitionbuild #53491 : non échecs (exécution partielle) build # 53503 : échecs de fragment build # 53570 : échecs de fragment build # 53583 : échecs de fragment build # 53599 : échecs de fragment build # 53601 : échecs de fragment build # 53748 : échecs de fragment build # 53753 : tous les fragments ont été transmis build # 53757 : aucun échec (exécution partielle) build # 53759 : aucun échec (exécution partielle) build # 53787 : aucun échec (exécution partielle) build # 53811 : tous les fragments ont été réussis build # 53952 : aucun échec (exécution partielle) build # 53992 : échecs de fragment build # 53999 : échecs de fragment build # 54007 : aucun échec (exécution partielle) build # 54012 : tous les fragments ont réussi build #54015 : échecs de fragments, build #54017 : tous les fragments ont réussi, build #54022 : échecs de fragments, build #54026 : aucun échec (exécution partielle) build #54030 : échecs de fragments, build #54033 : tous les fragments ont réussi, build #54040 : échecs de fragments, build #54047 : échecs de fragments, build #54049 : échecs de fragmentsbuild #54055 : échecs de fragmentsbuild #54057 : échecs de fragmentsbuild #54064 : échecs de fragmentsbuild #54074 : échecs de fragmentsbuild #54093 : échecs de fragmentsbuild #54161 : échecs de fragmentsbuild #54186 : échecs de fragmentsbuild #54189 : échecs de fragmentsbuild #54196 : échecs de fragmentsbuild #54202 : tous les fragments ont été réussis
✓
Windows x64 · 8 fragments
build #53090 : échecs de fragments, build #53094 : échecs de fragments, build #53095 : échecs de fragments, build #53106 : échecs de fragments, build #53109 : échecs de fragments, build #53123 : échecs de fragments, build #53127 : échecs de fragments, build #53130 : échecs de fragments, build #53131 : échecs de fragmentsbuild #53133 : échecs de fragmentsbuild #53134 : échecs de fragmentsbuild #53135 : échecs de fragmentsbuild #53143 : échecs de fragmentsbuild #53149 : échecs de fragmentsbuild #53159 : échecs de fragmentsbuild #53164 : échecs de fragmentsbuild #53167 : échecs de fragmentsbuild #53172 : échecs de fragmentsbuild #53176 : échecs de fragmentsbuild #53188 : échecs de fragmentsbuild #53194 : échecs de fragmentsbuild #53208 : échecs de fragmentsbuild #53212 : échecs de fragmentsbuild #53213 : échecs de fragmentsbuild #53214 : échecs de fragmentsbuild #53216 : échecs de fragmentsbuild #53222 : échecs de fragmentsbuild #53229 : échecs de fragmentsbuild #53236 : échecs de fragmentsbuild #53241 : échecs de fragmentsbuild #53260 : échecs de fragmentsbuild #53265 : échecs de fragmentsbuild #53271 : échecs de fragmentsbuild #53280 : échecs de fragmentsbuild #53298 : échecs de fragmentsbuild #53304 : échecs de partition build # 53327 : tous les fragments ont réussi build # 53340 : tous les fragments ont réussi build # 53360 : aucun échec (exécution partielle) build # 53419 : échecs de fragment build # 53431 : échecs de fragment build # 53458 : échecs de fragment build # 53470 : aucun échec (exécution partielle) build # 53485 : échecs de fragment build # 53491 : aucun échec (exécution partielle) build # 53503 : échecs de fragment build # 53514 : aucun échec (exécution partielle) build # 53565 : aucun échec (exécution partielle) build # 53570 : échecs de fragment build # 53599 : échecs de fragment build # 53745 : échecs de fragment build # 53748 : échecs de fragment build #53753 : échecs de partition, build #53757 : échecs de fragment, build #53759 : échecs de fragment, build #53762 : échecs de fragment, build #53769 : échecs de fragment, build #53781 : échecs de fragment, build #53787 : échecs de fragment, build #53808 : échecs de fragment, build #53811 : échecs de fragment, build #53852 : échecs de fragments, build #53863 : échecs de fragments, build #53883 : échecs de fragments, build #53893 : échecs de fragments, build #53914 : tous les fragments ont réussi, build #53933 : tous les fragments ont réussi, build #53952 : tous les fragments ont réussi, build #53973 : aucun échec (exécution partielle) build #53983 : échecs de fragmentsbuild #53992 : échecs de fragmentsbuild #53999 : échecs de fragmentsbuild #54002 : aucun échec (exécution partielle)build #54004 : aucun échec (exécution partielle)build #54007 : échecs de fragmentsbuild #54008 : aucun échec (exécution partielle)build #54012 : tous les fragments ont été transmisbuild #54015 : tous fragments passésbuild #54017 : tous les fragments ont réussi àbuild #54022 : échecs de fragmentsbuild #54026 : tous les fragments ont réussi àbuild #54030 : tous les fragments ont réussi àbuild #54033 : tous les fragments ont réussi àbuild #54040 : tous les fragments ont réussi àbuild #54047 : échecs de fragmentsbuild #54049 : échecs de fragments à construire #54055 : échecs de partition build #54057 : échecs de partition build #54064 : tous les fragments ont réussibuild #54074 : tous les fragments ont réussibuild #54083 : tous les fragments ont réussibuild #54093 : tous les fragments ont réussibuild #54144 : échecs de fragmentbuild #54161 : échecs de fragmentbuild #54186 : fragment failedsbuild #54189 : échecs de fragment build #54196 : échecs de fragment build #54202 : tous les fragments ont été transmis
✓
Windows arm64 · 8 fragments
build #53090 : échecs de fragments, build #53095 : échecs de fragments, build #53106 : échecs de fragments, build #53109 : échecs de fragments, build #53123 : échecs de fragments, build #53127 : échecs de fragments, build #53130 : échecs de fragments, build #53131 : échecs de fragments, build #53134 : échecs de fragmentsbuild #53135 : échecs de fragmentsbuild #53149 : échecs de fragmentsbuild #53159 : échecs de fragmentsbuild #53164 : échecs de fragmentsbuild #53167 : échecs de fragmentsbuild #53172 : échecs de fragmentsbuild #53176 : échecs de fragmentsbuild #53188 : échecs de fragmentsbuild #53194 : échecs de partition build # 53208 : échecs de partition build # 53212 : échecs de partition build # 53213 : échecs de partition build # 53214 : échecs de partition build # 53216 : échecs de partition build # 53222 : échecs de partition build # 53229 : échecs de partition build # 53236 : aucun échec (exécution partielle) build #53241 : échecs de fragments, build #53260 : échecs de fragments, build #53265 : échecs de fragments, build #53271 : échecs de fragments, build #53304 : échecs de fragments, build #53327 : tous les fragments ont réussi, build #53340 : tous les fragments ont réussi, build #53360 : aucun échec (exécution partielle) build #53419 : non échecs (exécution partielle) build # 53431 : échecs de fragment build # 53458 : aucun échec (exécution partielle) build # 53485 : échecs de fragment build # 53491 : aucun échec (exécution partielle) build # 53503 : aucun échec (exécution partielle) build # 53599 : échecs de fragment build # 53748 : échecs de fragment build # 53753 : fragment échecsbuild #53757 : échecs de fragmentsbuild #53759 : échecs de fragmentsbuild #53762 : échecs de fragmentsbuild #53787 : échecs de fragmentsbuild #53808 : échecs de fragmentsbuild #53811 : échecs de fragmentsbuild #53852 : échecs de fragmentsbuild #53863 : échecs de fragmentsbuild #53883 : fragment échecsbuild #53893 : échecs de fragmentsbuild #53914 : aucun échec (exécution partielle)build #53933 : tous les fragments passésbuild #53952 : tous les fragments passésbuild #53983 : échecs de fragmentsbuild #53992 : échecs de fragmentsbuild #53999 : échecs de fragmentsbuild #54007 : aucun échec (exécution partielle)build #54012 : tous les fragments ont réussi la build #54015 : tous les fragments ont réussi la build #54017 : tous les fragments ont réussi la build #54022 : les échecs de la partition ont réussi la build #54026 : tous les fragments ont réussi la build #54030 : aucun échec (exécution partielle) build #54033 : tous les fragments ont réussi la build #54040 : tous les fragments ont réussi la build #54047 : échecs de partition, build #54049 : échecs de partition, build #54055 : aucun échec (exécution partielle) build #54057 : échecs de partition, build #54064 : tous les fragments ont réussi à build #54074 : tous les fragments ont réussi à build #54083 : aucun échec (exécution partielle) build #54093 : tous les fragments ont réussi à build #54144 : échecs de partition, build #54161 : échecs de fragment, build #54186 : échecs de fragment, build #54189 : échecs de fragment, build #54196 : échecs de fragment, build #54202 : tous les fragments ont été transmis
✓
Les fragments de test de chaque build CI, par plate-forme, sur 135 builds qui ont exécuté des tests (420 extraits de BuildKite). Vert vif : chaque fragment est passé. Vert faible : aucun échec, mais la course a été écourtée (remplacée). Rouge : au moins une partition a échoué. Chaque voie est marquée lors du premier passage de sa suite complète : les 60 fragments de Linux étaient verts presque une journée complète avant Windows. Les plates-formes ont continué à vaciller en rouge jusqu'à ce que les derniers tests échoués tombent ; la version finale entièrement verte était #54202.
Le reste du temps précédant la fusion a été simple. Un flux de travail qui consistait en boucle à corriger les échecs des tests CI pour chaque plate-forme jusqu'à ce qu'il n'y ait plus d'échecs de test. Plusieurs workflows pour le nettoyage lié à Windows, pour dédupliquer le code, pour réduire les utilisations dangereuses et pour nettoyer de manière générale une partie du code.
Fusion de la réécriture Rust
Une fois que 100 % de la suite de tests de Bun ont réussi en CI sur toutes les plateformes (et j'ai vérifié manuellement que les tests étaient effectivement en cours d'exécution et n'étaient pas ignorés), j'ai exécuté un tas de commandes localement pour tester les choses - puis j'ai appuyé sur le bouton de fusion.
La fusion avec main n'est pas une version versionnée. À ce stade, j'étais suffisamment confiant pour aller de l'avant et m'engager dans la réécriture, mais pas encore assez confiant pour la publier.
Statistiques
Au maximum, nous exécutions 4 de ces workflows à la fois, chacun dans un arbre de travail distinct, chacun avec 16 Claudes par workflow. Environ 64 Claudes à la fois.
git log · claude/phase-a-portpeak : 58 commits en une minute
52
valide
+796,601
lignes écrites, réécritures incluses
Mardi 5 mai, 00h39 PDT
premier brouillon de 100 fichiers batchPR #30412 ouvert et fusionné
· phase-a : projet de tête de lot-1 (100 fichiers)+12 379−0
· phase-b1 : chemins + compilation de chaînes (brouillons de porte ; surface minimale d'encodage/chaînes/lexer)+2 312−2 149
Les 6 502 commits (fusions exclues), rejoués. Les barres roses sont pour la plupart du nouveau code ; les barres cyan sont pour la plupart des suppressions. Le compteur de lignes compte chaque réécriture en cours de route – le différentiel obtenu était de +1 009 272. Le journal est constitué de vrais messages de validation.
0 test ignoré ou supprimé
11 jours (3 mai → fusionné le 14 mai) · 6 778 commits
| Plateforme | appels expect() | Tests | Fichiers |
|---|---|---|---|
| Debian 13 x64 | 1,386,826 | 60,624 | 4,174 |
| macOS 14 arm64 | 1,259,953 | 58,850 | 4,175 |
| Windows 2019 x64 | 1,007,544 | 57,337 | 4,173 |
Avant la fusion, cela nécessitait 5,9 milliards de jetons d'entrée non mis en cache, 690 millions de jetons de sortie et 72 milliards de lectures de jetons d'entrée mis en cache, soit environ 165 000 $ au prix de l'API. À la main, je pense que cela aurait pris environ un an à 3 ingénieurs avec un contexte complet sur la base de code, période pendant laquelle nous ne serions pas en mesure d'améliorer la compatibilité Node.js, de corriger des bugs, de résoudre des problèmes de sécurité ou d'implémenter de nouvelles fonctionnalités. Nous n'aurions jamais fait ça. L'alternative réaliste était de ne rien faire et de continuer à corriger les bugs en haut de cet article pour toujours.
C'est à la pointe de ce qui est possible aujourd'hui. J'ai utilisé une version préliminaire de Claude Fable 5, un modèle de classe Mythos. Les flux de travail dynamiques de Claude Code ont permis à 64 Claude de fonctionner pendant 11 jours (sinon, j'aurais dû écrire mon propre harnais pour y parvenir).
Le travail continue
Depuis la fusion du port Rust, nous avons effectué 11 séries d'examens de sécurité de Claude Code Sécurité et corrigé les résultats.
Nous avons également ajouté un fuzzing guidé 24h/24 et 7j/7 de chaque analyseur dans Bun : JavaScript, TypeScript, JSX, CSS, JSON5, JSONC, TOML, YAML, Markdown, INI, Bun scripts Shell, plages de semestres, fichiers .patch et couleurs CSS. Le fuzzer envoie automatiquement les bogues qu'il trouve à Claude pour qu'il soumette un PR reproduisant et corrigeant, et les humains examinent les PR. Jusqu'à présent, nos analyseurs ont été exécutés 100 milliards de fois, ce qui a donné lieu à environ 15 PR.
Au moment de la rédaction, environ 4 % du code Rust de Bun se trouve dans un bloc unsafe (~13 000 mots-clés unsafe sur ~27 000 lignes / ~780 000 lignes), et 78 % de ces blocs sont une seule ligne : un pointeur provenant de C++ ou un appel dans une bibliothèque C. Je m'attends à ce que ce nombre diminue au fil du temps à mesure que nous refactorisons d'un port Zig fidèle (qui n'avait pas de mot-clé unsafe greppable) à un Rust idiomatique, mais nous allons continuer à utiliser des bibliothèques C & C++ comme JavaScriptCore donc il aura toujours plus de unsafe que de purs projets Rust.
Erreurs de portage
L'objectif de la réécriture Rust est la stabilité, mais il serait impossible de proposer un changement massif comme celui-ci et d'introduire des régressions nulles.
Cette réécriture a introduit 19 régressions connues, dont chacune a été corrigée.
La plupart des régressions provenaient d'un code syntaxiquement identique dans les deux langues mais sémantiquement différent.
Effet secondaire à l'intérieur de debug_assert!
Ces deux extraits se ressemblent mais se comportent différemment. assert de Zig est une fonction, donc son argument s'exécute dans chaque build. Le debug_assert! de Rust est une macro, donc dans les versions de version, l'intégralité de l'ion express est effacée, y compris l'appel insert_stale.
// Zig:
if (dev.framework.react_fast_refresh) |rfr| {
assert(try dev.client_graph.insertStale(rfr.import_source, false) == IncrementalGraph(.client).react_refresh_index);
}
// Rust:
if let Some(rfr) = &dev.framework.react_fast_refresh {
debug_assert!(dev.client_graph.insert_stale(&rfr.import_source, false)? == react_refresh_index);
}
insert_stale ajoute un fichier au graphique de rechargement à chaud du serveur de développement frontend. Dans les versions finales, il a cessé de fonctionner et HMR s'est cassé dans certains cas pour les projets avec des routes HTML qui utilisent React tandis qu'un fichier rechargé à chaud est invalidé : Cannot destructure property 'isLikelyComponentType' of 'k'. Les versions de débogage ont fonctionné. #30678
Tranches de longueur impaire
Bun (antérieur aux transtypages intégrés prenant en charge les tranches) utilisait @divTrunc et ignorait un octet impair de fin. bytemuck::cast_slice panique à la place. Blob.text() sur une marque d'ordre d'octets UTF-16 suivie d'un nombre impair d'octets a arrêté de renvoyer une chaîne et a paniqué le processus. Nous avons recommencé à ignorer l'octet impair : &buf[..buf.len() & !1]. #31188
Vérifications des limites
Sur macOS et Linux, nous avons compilé le code Zig de Bun avec ReleaseFast, ce qui supprime les vérifications de limites. Les versions de Rust les conservent.
Bun intègre les noms de fichiers longs dans une liste globale qui se répartit en blocs de débordement. Le code Zig d'origine dimensionnait chaque bloc à count / 4, soit 2 048. Le port a laissé un espace réservé :
/// ... so use a nonzero stand-in until Phase B threads the
/// per-instantiation value through.
pub const BSS_OVERFLOW_BLOCK_SIZE: usize = 64;
Cela a abaissé le plafond de 8,4 millions de noms de fichiers internés à 270 272, que les projets réels ont atteint, et a rendu accessible un ptrs[4095] un par un que nous avons porté à partir de Zig. Rust a paniqué au lieu d'écrire après la fin. Zig paniquerait également dans ce cas, si nous utilisions ReleaseSafe (nous ne l'avons fait que sous Windows). #31503
comptime chaînes de format
Output.pretty réécrit les marqueurs de couleur <r> et <d> dans les échappements ANSI. Dans Zig, fmt vaut comptime, donc les marqueurs ont disparu avant que les arguments ne soient remplacés. Les fonctions Rust n'ont pas de paramètres de comptime, donc Output::pretty n'a vu que la chaîne terminée et a également réécrit les marqueurs sur les arguments.
// Zig:
pub inline fn pretty(comptime fmt: string, args: anytype) void;
Output.pretty("<r>{f}<r>", .{hyperlink});
// Rust:
pub fn pretty(payload: impl PrettyFmtInput);
Output::pretty(format_args!("<r>{}<r>", hyperlink));
bun update -i imprime les noms de packages sous forme d'hyperliens OSC 8, terminés par ESC \. Cette barre oblique inverse se trouve juste avant le < du <r> final, l'analyseur de marqueurs le mange et le r s'imprime sous forme de texte.
Dans Rust il doit s'agir d'une macro : bun_core::pretty!("<r>{}<r>", hyperlink). #30693
Bun est meilleur en Rust
Jusqu'à présent, Bun la version 1.4.0 corrige 128 bugs qui se reproduisent dans la version 1.3.14. Celles-ci vont des fuites de mémoire aux plantages en passant par le texte d'aide mal coloré.
Utilisation de la mémoire réduite
Rust dispose d'un puissant outil au niveau du langage pour nettoyer la mémoire : Drop. Lorsque Drop est implémenté, la fonction drop est automatiquement appelée chaque fois que la valeur sort de la portée.
impl Drop for Bytes {
fn drop(&mut self) {
if !self.pinned.is_empty() {
JSC__JSValue__unpinArrayBuffer(self.pinned);
}
}
}
Dans Zig, defer peut être utilisé pour exécuter du code à la fin d'une portée :
const bytes: ArrayBuffer = try .fromPinned(global, value);
defer bytes.unpin();
Dans Zig, defer doit être ajouté à chaque site d'appel individuel susceptible de nécessiter un nettoyage. Il est facile de finir par oublier de nettoyer (une fuite de mémoire) ou d'exécuter le code de nettoyage deux fois dans un code de gestion des erreurs rarement atteint (un double libre). Dans Rust, Drop s'exécute automatiquement lorsque la valeur n'est plus accessible - échangeant "aucun flux de contrôle caché" contre une arme à pied commune.
Drop correction de plusieurs fuites de mémoire dans Bun liées aux chemins de fichiers dans le code de gestion des erreurs.
Nous avons corrigé toutes les fuites de mémoire instrumentable
Nous avons amélioré l'intégration de LeakSanitizer de Bun pour suivre toutes les allocations de mémoire de code natif.
Voici un exemple : chaque appel Bun.build() en cours de processus a entraîné une fuite de plusieurs mégaoctets de mémoire : le texte source analysé et les tables de symboles AST ont survécu à la version à laquelle ils appartenaient.
// Bundle the same 60-module project 2,000 times in one process
for (let i = 0; i < 2_000; i++) {
await Bun.build({
entrypoints: ["./index.js"],
minify: true,
sourcemap: "external",
});
}
Dans Bun v1.3.14, chaque build perd environ 3 Mo pour toujours : les outils tels que les serveurs de développement qui sont regroupés pour chaque requête finissent par manquer de mémoire. Dans Bun v1.4.0, la mémoire diminue :
| Constructions | Bun v1.3.14 | Bun v1.4.0 |
|---|---|---|
| 500 | 1 914 Mo | 526 Mo |
| 1,000 | 3 506 Mo | 586 Mo |
| 1,500 | 5 097 Mo | 608 Mo |
| 2,000 | 6 745 Mo | 609 Mo |
Une tentative précédente visant à effectuer cette opération dans Zig n'a pas été fusionnée car l'absence d'équivalent de Drop rendait plus difficile la fusion en toute confiance.
Taille binaire plus petite
Les premières modifications apportées à la réécriture Rust ont réduit la taille des binaires de 3,8 Mo sous Windows, de 5,5 Mo sous macOS et de 6,8 Mo sous Linux. Cela est dû en grande partie au fait que nous avons utilisé trop de comptime dans notre code Zig.
Après ce rétrécissement initial, l'équipe a exploré d'autres possibilités de réduction de la taille binaire en utilisant des optimisations d'éditeur de liens telles que le pliage de code identique, la suppression des données inutilisées de l'ICU et la décompression paresseusement de petites parties de libicu avec un dictionnaire zstd à la demande.
Combinée à la réécriture Rust, aux modifications ICU et au pliage de code identique, **Bun la taille binaire de Bun diminue de ~20 % sous Linux et Windows.
| Version | Plateforme | Taille |
|---|---|---|
| Bun v1.4.0 (canari) | Windows | 76 Mo |
| Bun v1.3.14 | Windows | 94 Mo |
| Bun v1.4.0 (canari) | Linux | 70 Mo |
| Bun v1.3.14 | Linux | 88 Mo |
Utilisation réduite de l'espace de pile
L'analyseur TOML et tous les autres analyseurs à descente récursive dans Bun (JSON, YAML, JavaScript, TypeScript et plus) utilisent désormais moins d'espace de pile.
Cela a provoqué quelques échecs de tests avant de fusionner la réécriture Rust :
bun test v1.3.14-canary.1 (e99311e58)
.......
105 | });
106 |
107 | it("Bun.TOML.parse throws on deeply nested inline tables instead of crashing", () => {
108 | const depth = 25_000;
109 | const deepToml = "a = " + "{ b = ".repeat(depth) + "1" + " }".repeat(depth);
110 | expect(() => Bun.TOML.parse(deepToml)).toThrow(RangeError);
^
error: expect(received).toThrow(expected)
Expected constructor: RangeError
Received function did not throw
Received value: {
a: {
b: {
b: {
b: {
b: {
b: {
b: {
b: {
b: [Object ...],
},
},
},
},
},
},
},
},
}
at <anonymous> (/var/lib/buildkite-agent/build/test/js/bun/resolve/toml/toml.test.js:110:42)
✗ Bun.TOML.parse throws on deeply nested inline tables instead of crashing [2907.64ms]
Rust émet les éléments intrinsèques llvm.lifetime.start et llvm.lifetime.end de LLVM pour les variables de pile lorsqu'elles ne sont plus utilisées, ce qui permet à LLVM de réutiliser les emplacements d'espace de pile. Cela permet aux fonctions volumineuses avec des étendues imbriquées d'utiliser beaucoup moins d'espace de pile.
Auparavant, nous avons résolu manuellement un problème ouvert en refactorisant des fonctions particulièrement volumineuses en de nombreuses fonctions plus petites.
2 % à 5 % plus rapide
Rust prend en charge l'optimisation du temps de liaison entre langages C/C++ et Rust, ce qui permet l'intégration dans tous les langages de programmation (c'est vraiment cool !!).
Nous avons comparé Bun v1.3.14 à Bun v1.4.0 sur Linux x64 (EC2, Xeon Platinum 8488C). Débit HTTP mesuré avec oha par rapport aux serveurs hello-world, charges de travail des applications mesurées avec hyperfine.
Débit HTTP (requête/s, moyenne de 3 tours)
| serveur | Bun v1.3.14 | Bun v1.4.0 | Δ |
|---|---|---|---|
| Bun.serve | 169.6k | 177.7k | +4.8% |
| node:http | 103.8k | 108.5k | +4.5% |
| Elysia | 158.9k | 163.3k | +2.8% |
| express | 64.5k | 66.6k | +3.2% |
| fastify | 91.5k | 95.9k | +4.8% |
Applications/CLI (hyperfine)
| charge de travail | Bun v1.3.14 | Bun v1.4.0 | Δ |
|---|---|---|---|
| next build | 13.62 s | 13.03 s | +4.5% |
| vite build (tsc + vite) | 1.69 s | 1.65 s | +2.2% |
| tsc -b --force | 0.94 s | 0.89 s | +4.7% |
Production
Prisma a lancé la version bêta publique de Prisma Compute lors de la réécriture Rust de Bun.
"Nous avons rencontré des fuites de mémoire et un pool de connexions qui n'a pas pu être récupéré après la pause et la reprise d'une VM. Lorsque la réécriture Rust est apparue, nous l'avons testée avec les mêmes modes de défaillance. Elle les a parfaitement gérés." - Alexeï Orlenko
Claude Code v2.1.181 (publié le 17 juin) et versions ultérieures utilisent le port Rust de Bun. Le démarrage est devenu 10 % plus rapide sous Linux, mais sinon, presque personne ne l'a remarqué. C'est bien de s'ennuyer.
Expédition
Bun v1.3.14 était la dernière version de Bun écrite en Zig. Bun v1.4.0 sera la première version de Bun écrite en Rust. Il est désormais disponible en version Canary. Veuillez signaler tout problème que vous rencontrez :
bun upgrade --canary
Maintenabilité
Pour moi et pour l'équipe, notre nouvelle base de code Rust ressemble beaucoup à l'ancienne base de code Zig. Par exemple, voici un extrait du code Zig d'origine et du nouveau code Rust :
pub fn canMergeSymbols(
scope: *Scope,
existing: Symbol.Kind,
new: Symbol.Kind,
comptime is_typescript_enabled: bool,
) SymbolMergeResult {
if (existing == .unbound) {
return .replace_with_new;
}
if (comptime is_typescript_enabled) {
// In TypeScript, imports are allowed to silently collide with symbols within
// the module. Presumably this is because the imports may be type-only:
//
// import {Foo} from 'bar'
// class Foo {}
//
if (existing == .import) {
return .replace_with_new;
}
// ...
}
// ...
}
pub fn can_merge_symbol_kinds<const IS_TYPESCRIPT_ENABLED: bool>(
scope_kind: Kind,
existing: symbol::Kind,
new: symbol::Kind,
) -> SymbolMergeResult {
if existing == symbol::Kind::Unbound {
return SymbolMergeResult::ReplaceWithNew;
}
if IS_TYPESCRIPT_ENABLED {
// In TypeScript, imports are allowed to silently collide with symbols within
// the module. Presumably this is because the imports may be type-only:
//
// import {Foo} from 'bar'
// class Foo {}
//
if existing == symbol::Kind::Import {
return SymbolMergeResult::ReplaceWithNew;
}
// ...
}
// ...
}
Quiconque comprend le code Zig original comprend le code Rust traduit mécaniquement. J'ai examiné le PR de réécriture Rust original en vérifiant que les agents contradictoires de révision du code détectaient correctement les écarts entre le code Zig et le code Rust, qu'ils s'assuraient que le guide de portage et le guide de durée de vie étaient suivis, et qu'ils lisaient également manuellement une grande partie du code moi-même côte à côte avec le Zig vs Rust.
Quelle est la prochaine étape
Bun v1.4 rend Bun plus rapide, plus petit, utilise moins de mémoire et donne à l'équipe des outils incroyablement puissants pour améliorer systématiquement la stabilité à l'avenir : le vérificateur d'emprunt de Rust, Miri (qui s'exécute sur une partie croissante de code dans CI), LeakSanitizer et un fuzzing guidé par la couverture 24h/24 et 7j/7 pour les analyseurs. Il reste encore plus à refactoriser, mais les choses commencent bien.
Cette Rust réécriture aurait nécessité un an de travail à une équipe d'ingénieurs disposant d'un contexte complet sur la base de code. Avec 1 ingénieur utilisant Fable et surveillant de près Claude Code, nous sommes passés du début à 100 % de réussite de la suite de tests sur toutes les plateformes en 11 jours.
Un ingénieur peut faire bien plus aujourd'hui qu'il y a un an.
