2 points par GN⁺ 3 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Bun, qui a démarré en Zig, est devenu un runtime téléchargé plus de 22 millions de fois par mois, mais l’interaction entre un moteur JavaScript à GC et une gestion manuelle de la mémoire a provoqué des problèmes de stabilité récurrents, servant de déclencheur au passage à Rust
  • Plutôt que de faire migrer manuellement 535 496 lignes de code Zig par des humains pendant un an, l’équipe a exécuté en parallèle pendant 11 jours environ 50 workflows dynamiques de Claude Code et jusqu’à 64 instances de Claude
  • Le portage a été validé avec PORTING.md, LIFETIMES.tsv, 1 implémenteur et au moins 2 reviewers adversariaux, ainsi que la suite de tests TypeScript existante, et a réussi à 100 % sur la CI de 6 plateformes
  • Après le passage à Rust, Bun v1.4.0 corrige 128 bugs reproductibles dans v1.3.14, répare toutes les fuites mémoire instrumentables et réduit d’environ 20 % la taille des binaires Linux et Windows
  • Bun v1.3.14 est la dernière version Zig et v1.4.0 la première version Rust, disponible en canary ; l’équipe utilise le borrow checker, Miri, LeakSanitizer et le fuzzing 24/7 basé sur la couverture comme outils d’amélioration de la stabilité

Bun, né en Zig, et les problèmes de stabilité

  • Bun a commencé comme un projet de portage ligne par ligne de Go vers Zig du transpileur JavaScript et TypeScript d’esbuild
  • Le premier code Zig a été écrit le 16 avril 2021, et le contrôle bas niveau ainsi que la conception orientée performances de Zig ont rendu possible l’implémentation initiale de Bun
  • Les premières versions de Bun ont été écrites en Zig par une seule personne pendant un an, avec un périmètre très large
    • Transpileur, minifieur et bundler JavaScript, TypeScript et CSS
    • Gestionnaire de paquets compatible npm
    • Test runner similaire à Jest
    • Résolution de modules compatible Node.js et TypeScript
    • Client HTTP/1.1 et WebSocket
    • Implémentation d’API Node.js comme fs, net, tls
  • Aujourd’hui, le CLI Bun est téléchargé plus de 22 millions de fois par mois, Claude Code et OpenCode l’utilisent comme runtime, et Vercel, Railway, DigitalOcean, entre autres, proposent une prise en charge first-party

Bugs récurrents de sûreté mémoire

  • Parmi les bugs corrigés dans Bun v1.3.14 figurent des use-after-free, double-free, fuites mémoire, accès out-of-bounds et race conditions
    • Heap-use-after-free causé par l’appel de .reset() pendant un .write() async de node:zlib
    • Use-after-free dans node:http2, où un callback JS réentrant provoque un rehash de hashmap, invalidant un pointeur de stream interne
    • Problème dans UDPSocket.send() et sendMany() où un callback valueOf() ou toString() détache un ArrayBuffer
    • Crashs et lectures out-of-bounds dans Buffer#copy et Buffer#fill dus au détachement ou au redimensionnement d’un ArrayBuffer pendant la coercition des arguments
    • Fuites mémoire liées à crypto.scrypt, tlsSocket.setSession() et fs.watch()
    • Double-free dans le parser CSS lors du traitement des vendor prefixes et des arrière-plans multi-couches
    • Crash par race condition sur MessageEvent lors d’accès concurrents à BroadcastChannel ou MessagePort
  • Plusieurs dispositifs étaient déjà utilisés pour renforcer la stabilité
    • Patch du compilateur Zig pour prendre en charge Address Sanitizer et exécution de la suite de tests ASAN à chaque commit
    • Distribution de builds Windows ReleaseSafe avec vérifications de sûreté Zig
    • Fuzzing 24/7 des API du runtime Bun avec Fuzzilli
    • Exploitation de nombreux tests end-to-end de fuites mémoire
  • La position n’est pas que Zig serait en soi le problème ; la principale source des problèmes de stabilité venait de l’obligation de manipuler ensemble des valeurs sous GC et de la mémoire gérée manuellement

Pourquoi Rust a été choisi

  • JavaScript est un langage à GC, et les moteurs comme JavaScriptCore et V8 imposent des règles strictes concernant la gestion des exceptions et le GC
  • Comme C, Zig ne gère pas automatiquement la mémoire ; il n’a ni constructeurs ni destructeurs, et le nettoyage doit le plus souvent être explicité avec defer à chaque call site
  • Dans Bun, gérer correctement la lifetime des valeurs GC et des valeurs gérées manuellement constituait une source majeure de problèmes de stabilité
    • Il fallait vérifier où les octets alloués étaient libérés
    • Il fallait garantir qu’ils n’étaient libérés qu’une seule fois
    • Il fallait vérifier correctement la gestion des exceptions JavaScript
    • Il fallait s’assurer que les pointeurs GC étaient visibles par le scanner de pile conservatif
  • Le mécanisme de cleanup de Zig repose sur defer et errdefer explicites, C++ utilise des destructeurs et le move, tandis que Rust utilise Drop
  • Le code Zig existant de Bun combinait lifetimes d’arena, reference counting et revues minutieuses
  • On peut faire appliquer des règles d’ownership par un guide de style et des code reviews, mais dans du code Rust sûr, les use-after-free, double-free et free manquants dans les chemins d’erreur deviennent des erreurs de compilation
  • Environ 20 % du code de Bun est en C++, et plusieurs bibliothèques C/C++ sont intégrées
    • JavaScriptCore
    • uWebSockets et usockets
    • lshpack et lsquic
    • BoringSSL
    • SQLite
  • C++ aurait aussi pu être une option, mais il reste dépendant des guides de style et des code reviews, et des corruptions mémoire ainsi que des fuites peuvent survenir même avec ASAN

Stratégie de réécriture : en une fois, mécaniquement

  • Le code Zig existant de Bun comptait 535 496 lignes hors commentaires, et une réécriture traditionnelle était estimée à environ un an de travail pour une petite équipe d’ingénieurs
  • Comme il n’était pas possible d’arrêter pendant un an les corrections de bugs, les correctifs de sécurité et le développement de fonctionnalités, un portage mécanique minimisant les changements de comportement utilisateur a été choisi comme approche la moins risquée
  • La suite de tests de Bun étant écrite en TypeScript, elle ne dépendait pas du langage d’implémentation du runtime
  • Une réécriture incrémentale aurait nécessité de créer du code temporaire en espérant le supprimer plus tard, ce qui a été jugé douloureux à court et moyen terme ; l’équipe a donc tout migré en une seule fois
  • Le code Rust a été écrit pour ressembler à du code Zig transpilé, avec l’objectif, après Bun v1.4, de réduire progressivement unsafe et de refactorer vers du Rust idiomatique

Workflows dynamiques de Claude Code

  • Pour la réécriture en Rust, environ 50 workflows dynamiques ont été exécutés en continu dans Claude Code pendant 11 jours
  • Les workflows allaient de la rédaction du guide de portage à la conversion de fichiers, la correction d’erreurs de compilation, la restauration de subcommands, la réussite de toute la suite de tests et de grands cleanups
    • Génération d’un guide de portage mappant les patterns et types Zig vers des patterns et types Rust
    • Portage mécanique de tous les fichiers .zig en fichiers .rs conformément à PORTING.md et LIFETIMES.tsv
    • Correction des erreurs du compilateur par crate
    • Restauration du comportement de subcommands comme bun test et bun build
    • Réussite de l’ensemble de la suite de tests
    • Refactorings et cleanups à grande échelle
  • Pendant l’essentiel de la période, des humains lisaient la sortie des workflows, identifiaient les problèmes et les bugs, puis ajustaient les prompts pour que Claude corrige les boucles
  • En préparation, l’équipe a discuté pendant environ 3 heures avec Claude de la manière de mapper vers Rust les patterns de la base de code Zig, et le résultat a été sérialisé dans PORTING.md
  • Pour ajouter des lifetimes Rust au code de gestion manuelle de la mémoire, un workflow analysant les lifetimes de tous les champs de structures a été exécuté
    • Recherche des champs aux lifetimes complexes
    • Proposition de lifetimes
    • Revue par 2 agents reviewers adversariaux
    • Intégration des retours et enregistrement dans LIFETIMES.tsv

Méthode de revue contradictoire

  • Pour chaque Claude chargé de l’implémentation, un Claude relecteur contradictoire était placé dans une fenêtre de contexte distincte ; le relecteur ne recevait que le diff et avait pour consigne de supposer que le code était incorrect afin d’y trouver des bugs
  • La structure de base comprenait 1 implémenteur, au moins 2 relecteurs contradictoires et 1 fixer
  • Tous les bugs réellement détectés par les relecteurs passaient la compilation, mais posaient des problèmes de comportement
    • Comme uv_close est asynchrone, le Box<uv::Pipe> était drop à la fin du match arm, laissant libuv conserver de la mémoire libérée, ce qui provoquait un use-after-free et un double-free
    • Une erreur de timespec où l’utilisation de trunc() sur un file time négatif non entier produisait un nsec négatif
    • Une erreur où unwrap_or évalue ses arguments de façon eager, provoquant un panic dans le cas où le pourcentage de color-mix() est omis
  • Comme dans une revue humaine, les contextes de l’auteur et des relecteurs étaient séparés afin de réduire le biais pouvant venir du fait que l’implémenteur souhaite merger

Exécution du portage à grande échelle et parallélisation

  • Avant de porter les 1 448 fichiers .zig, la procédure a d’abord été validée sur 3 fichiers
    • 1 implémenteur écrivait le fichier .rs
    • 2 relecteurs vérifiaient que le comportement correspondait au .zig et que PORTING.md et LIFETIMES.tsv étaient respectés
    • 1 fixer appliquait les suggestions
  • Au début du portage complet, plusieurs Claude exécutaient git stash, git stash pop et git reset HEAD --hard, provoquant des conflits entre eux
  • Des règles ont ensuite été ajoutées au workflow pour interdire git stash, git reset, les commandes git autres que le commit de fichiers précis, ainsi que les commandes lentes comme cargo
  • Au final, 4 shards de workflow et 4 worktrees ont été utilisés, avec 16 Claude par shard qui committaient et poussaient des fichiers
  • Grâce à la parallélisation et à la préparation en amont, au pic, Claude écrivait environ 1 300 lignes de code par minute
  • Hors commit de merge, la branche de portage comptait 6 502 commits ; l’heure de pointe a atteint 695 commits, et le diff final landed représentait +1 009 272 lignes
  • Il y a aussi eu un problème dû au fait que les IOPS par défaut de l’instance EC2 n’avaient pas été augmentés : un seul grep lent pouvait bloquer les lectures et écritures disque pendant plusieurs minutes

Erreurs de compilation et séparation en crates

  • Une fois tout le code écrit, le workflow Claude a corrigé les erreurs du compilateur
  • La base de code Zig était en pratique une seule compilation unit, tandis que le code Rust devait être divisé en environ 100 crates pour accélérer la compilation
  • La catégorie d’erreurs la plus délicate était celle des dépendances circulaires
    • La seule PR de séparation en crates juste avant la réécriture en Rust ne suffisait pas
    • Un workflow séparé classait et consignait où placer le code ayant des dépendances circulaires
    • Un autre workflow effectuait ensuite ce refactoring
  • Après la résolution des dépendances circulaires, environ 16 000 erreurs du compilateur sont apparues
  • Ces erreurs ont été traitées en parallèle par crate
    • Exécution de cargo check dans chaque crate
    • Regroupement et enregistrement de la sortie par fichier
    • Correction des erreurs de compilation du crate concerné
    • Revue des changements par 2 relecteurs contradictoires
    • Application des corrections par 1 fixer
  • Il y a aussi eu un faux départ où Claude a interprété « rendre tous les crates compilables » comme la création de stubs de fonctions
  • Lorsqu’un schéma consistant à justifier des workarounds par de longs commentaires explicatifs est apparu, une règle de revue a été ajoutée : « si un commentaire de la longueur d’un paragraphe est nécessaire, le code est faux et il faut corriger le code »

Chemin jusqu’au passage des tests

  • Après le passage de cargo check, les problèmes ont été résolus dans l’ordre suivant : erreurs de linkage, panic immédiatement au démarrage, bun --version, puis exécution de bun test <file>
  • Un workflow a été utilisé pour enregistrer dans des fichiers les stacktraces d’échec par subcommand CLI, puis les corriger via une boucle implémenteur/relecteurs/fixer
  • Le workflow de fichiers de test shardait environ 100 fichiers de test aléatoires sur 4 worktrees, enregistrait les stacktraces et erreurs pour chaque échec, puis les corrigeait
  • La suite de tests contenait des tests de fuite mémoire et des tests d’intégration pouvant timeout en debug build
    • Un test qui exécute next dev et vérifie que le hot module reloading détecte 100 changements
    • Un stress test qui épuise le nombre maximal de sockets TCP
    • Des tests de lectures/écritures disque de l’ordre du gigaoctet
    • Un test qui spawn environ 10 000 processus
  • Pour l’isolation, systemd-run et les cgroups limitaient l’utilisation de la mémoire et du CPU et séparaient le pid namespace
  • Malgré cela, la machine a crashé plusieurs fois par manque d’espace disque
  • Deux jours après la première exécution CI, le nombre de fichiers de test en échec est passé de 972 à 23 ; un jour et demi plus tard, Linux était entièrement green
  • Au final, l’ensemble des tests CI est passé sur 6 plateformes
    • macOS x64
    • macOS arm64
    • Linux x64
    • Linux arm64
    • Windows x64
    • Windows arm64
  • Après 100 % de tests réussis, des humains ont vérifié manuellement que les tests étaient réellement exécutés et non skippés, puis ont mergé
  • Le moment du merge dans main n’était pas une versioned release : le niveau de confiance n’était pas encore suffisant pour une release, mais il l’était pour se consacrer pleinement à la rewrite

Taille des tests et coût

  • En 11 jours, du 3 mai au merge du 14 mai, 6 778 commits ont été créés
  • Les tests n’ont pas été supprimés ni skippés
  • La taille des tests par plateforme était la suivante
    • Debian 13 x64 : expect() 1 386 826 fois, 60 624 tests, 4 174 fichiers
    • macOS 14 arm64 : expect() 1 259 953 fois, 58 850 tests, 4 175 fichiers
    • Windows 2019 x64 : expect() 1 007 544 fois, 57 337 tests, 4 173 fichiers
  • Le travail pré-merge a consommé 5,9 milliards de tokens d’entrée non mis en cache, 690 millions de tokens de sortie et 72 milliards de lectures de tokens d’entrée mis en cache
  • Au tarif API, le coût était d’environ 165 000 dollars
  • L’estimation est que, fait manuellement, le travail aurait pris environ un an à 3 ingénieurs ayant le contexte complet de la codebase
  • Le modèle utilisé était la préversion de Claude Fable 5, avec une disclosure indiquant que Bun a été acquis par Anthropic en décembre 2025

Revue de sécurité, fuzzing et état de l’unsafe

  • Après le merge du port Rust, 11 rounds de revue de sécurité ont été menés avec Claude Code Security, et les findings ont été traités
  • Du fuzzing basé sur la couverture, 24/7, a été ajouté pour tous les parsers de Bun
    • JavaScript
    • TypeScript
    • JSX
    • CSS
    • JSON5
    • JSONC
    • TOML
    • YAML
    • Markdown
    • INI
    • scripts Bun Shell
    • plages semver
    • fichiers .patch
    • couleurs CSS
  • Le fuzzer envoie les bugs trouvés à Claude afin qu’il soumette des PR incluant reproduction et correctif ; les humains reviewent ensuite les PR
  • À ce jour, les parsers 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 se trouve dans des blocs unsafe
    • Environ 13 000 mots-clés unsafe
    • Environ 27 000 lignes / environ 780 000 lignes au total
    • 78 % des blocs unsafe tiennent sur une seule ligne et correspondent à des pointeurs venus de C++ ou à des appels à des bibliothèques C
  • Étant donné que Bun continue d’utiliser des bibliothèques C/C++ comme JavaScriptCore, il est indiqué que la quantité d’unsafe restera toujours supérieure à celle d’un projet Rust pur

Régressions découvertes après le passage à Rust

  • La réécriture en Rust était un changement de grande ampleur et a créé 19 régressions connues, toutes corrigées
  • La plupart provenaient de code dont la syntaxe est similaire dans les deux langages, mais dont la sémantique diffère
  • Effets de bord dans debug_assert!

    • En Zig, assert est une fonction, donc ses arguments sont exécutés dans tous les builds
    • En Rust, debug_assert! est une macro, donc toute l’expression est supprimée dans les builds de release
    • L’appel à insert_stale disparaissait dans les builds de release, ce qui cassait un cas HMR précis dans les projets de routes HTML utilisant React
    • Issue liée : #30678
  • Slices de longueur impaire

    • Le helper Zig de Bun reinterpretSlice(u16, bytes) utilisait @divTrunc et ignorait l’octet impair final
    • En Rust, bytemuck::cast_slice panique sur une longueur impaire
    • Il existait une régression où Blob.text() avec un nombre impair d’octets après un BOM UTF-16 faisait paniquer le process au lieu de retourner une chaîne
    • Le correctif consiste à ignorer de nouveau l’octet impair avec &buf[..buf.len() & !1]
    • Issue liée : #31188
  • Bounds checks

    • Le code Zig pour macOS et Linux était compilé en ReleaseFast, ce qui supprimait les bounds checks, tandis que les builds Rust de release les conservent
    • La taille du bloc d’overflow du resolver de modules de Bun était restée sur la valeur placeholder 64, abaissant le plafond de 8,4 millions de noms de fichiers internés à 270 272
    • Le off-by-one ptrs[4095] porté devenait atteignable dans des projets réels, et Rust déclenchait une panique au lieu d’une écriture hors limites
    • Issue liée : #31503
  • Chaînes de format comptime

    • Dans Output.pretty de Zig, fmt étant comptime, les marqueurs de couleur <r> et <d> étaient convertis en séquences d’échappement ANSI avant la substitution des arguments
    • La fonction Rust n’ayant pas de paramètre comptime, elle traitait les marqueurs dans la chaîne finale et réécrivait aussi les arguments à tort
    • Dans bun update -i, la terminaison des hyperlinks OSC 8 et le marqueur final <r> entraient en conflit, ce qui affichait r comme du texte
    • En Rust, il fallait utiliser la macro bun_core::pretty!("<r>{}<r>", hyperlink)
    • Issue liée : #30693

Bugs corrigés et fuites mémoire

  • Bun v1.4.0 corrige 128 bugs reproductibles dans v1.3.14
  • Le périmètre va des fuites mémoire et crashs jusqu’au texte d’aide mal coloré
  • En Rust, Drop appelle automatiquement la fonction drop lorsqu’une valeur sort de son scope
  • En Zig, il fallait ajouter defer à chaque call site, ce qui facilitait les oublis de cleanup ou les cleanups en double
  • Drop en Rust est un choix qui réduit les footguns courants, au prix de l’acceptation d’un control flow caché
  • Drop a corrigé plusieurs fuites mémoire liées aux chemins de fichiers dans le code de gestion des erreurs
  • L’intégration de LeakSanitizer dans Bun a été améliorée afin de suivre toutes les allocations mémoire du code natif
  • Toutes les fuites mémoire instrumentables ont été corrigées
  • Amélioration des fuites de Bun.build()

    • Dans l’ancien Bun v1.3.14, à chaque appel in-process de Bun.build(), le texte source parsé et la table de symboles de l’AST survivaient à la durée de vie du build, entraînant des fuites de plusieurs Mo
    • Dans un test bundlant 2 000 fois le même projet de 60 modules dans un seul process, v1.3.14 continuait à fuir environ 3 Mo à chaque build
    • Dans Bun v1.4.0, l’utilisation mémoire se stabilise
    • | Builds | Bun v1.3.14 | Bun 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 |

Taille du binaire, utilisation de la pile et performances

  • Les premiers changements de la réécriture en Rust ont à eux seuls réduit la taille du binaire
    • Windows : réduction de 3,8 MB
    • macOS : réduction de 5,5 MB
    • Linux : réduction de 6,8 MB
  • La principale cause était l’usage excessif de comptime dans le code Zig
  • Par la suite, l’Identical Code Folding, la suppression des données ICU inutilisées et la décompression différée de parties de libicu avec un dictionnaire zstd ont également été appliqués
  • En combinant la réécriture en Rust, les changements ICU et l’Identical Code Folding, la taille du binaire Bun diminue d’environ 20 % sous Linux et Windows
Version Plateforme Taille
Bun v1.4.0 canary Windows 76 MB
Bun v1.3.14 Windows 94 MB
Bun v1.4.0 canary Linux 70 MB
Bun v1.3.14 Linux 88 MB
  • Le parser TOML et les parsers recursive-descent de Bun utilisent moins d’espace de pile
  • La génération de code LLVM IR de Rust émet les intrinsèques llvm.lifetime.start et llvm.lifetime.end pour les variables de pile, ce qui permet à LLVM de réutiliser les emplacements de pile
  • Auparavant, il fallait refactorer manuellement les fonctions particulièrement grandes en plusieurs petites fonctions pour contourner une issue ouverte de Zig
  • Rust prend en charge la link-time optimization cross-language entre C/C++ et Rust, ce qui permet l’inlining entre langages
  • Benchmarks Linux x64

    • Bun v1.3.14 et Bun v1.4.0 ont été comparés sur Linux x64 avec une EC2 Xeon Platinum 8488C
    • Le débit HTTP a été mesuré avec oha, et les workloads applicatifs avec hyperfine
    • | server | Bun v1.3.14 | Bun v1.4.0 | Δ |
    • | --- | ---: | ---: | ---: |
    • | Bun.serve | 169,6k req/s | 177,7k req/s | +4,8 % |
    • | node:http | 103,8k req/s | 108,5k req/s | +4,5 % |
    • | Elysia | 158,9k req/s | 163,3k req/s | +2,8 % |
    • | express | 64,5k req/s | 66,6k req/s | +3,2 % |
    • | fastify | 91,5k req/s | 95,9k req/s | +4,8 % |
    • | workload | Bun v1.3.14 | Bun v1.4.0 | Δ |
    • | --- | ---: | ---: | ---: |
    • | next build | 13,62 s | 13,03 s | +4,5 % |
    • | vite build | 1,69 s | 1,65 s | +2,2 % |
    • | tsc -b --force | 0,94 s | 0,89 s | +4,7 % |

Cas d’usage réels et état des versions

  • Prisma a lancé la bêta publique de Prisma Compute sur la réécriture de Bun en Rust
  • Prisma indique avoir testé, dans la réécriture en Rust, des modes de défaillance avec un connection pool ne se rétablissant pas après un pause/resume de VM et des fuites mémoire, et que ces modes de défaillance ont été correctement gérés
  • Claude Code v2.1.181, ainsi que les versions publiées après le 17 juin, utilisent le portage de Bun en Rust
  • Le démarrage de Claude Code sous Linux est devenu 10 % plus rapide, mais à part cela, la plupart des utilisateurs ne l’auraient presque pas remarqué
  • Bun v1.3.14 est la dernière version de Bun écrite en Zig
  • Bun v1.4.0 est la première version de Bun écrite en Rust et est disponible en canary

Les outils obtenus par l’équipe et le travail restant

  • La nouvelle base de code Rust conserve une forme très proche de l’ancienne base de code Zig
  • Elle a été écrite de sorte qu’une personne comprenant le code Zig d’origine puisse aussi comprendre le code Rust traduit mécaniquement
  • La revue de la PR de réécriture en Rust a consisté à vérifier qu’un agent de revue contradictoire repérait correctement les incohérences entre Zig et Rust, ainsi que le respect du guide de portage et du guide des lifetimes, puis à faire lire par des humains une grande quantité de code côte à côte
  • Bun v1.4 rend Bun plus rapide et plus compact, réduit son utilisation mémoire et fournit des outils pour améliorer sa stabilité
    • Rust borrow checker
    • Miri
    • LeakSanitizer
    • fuzzing coverage-guided 24/7 ciblant le parser
  • Il reste encore des parties à refactorer, et bun-unsafe-audit est intégré
  • Un ingénieur a surveillé de près Fable et Claude Code, permettant d’atteindre en seulement 11 jours un état où toute la suite de tests passait sur toutes les plateformes

1 commentaires

 
GN⁺ 3 시간 전
Avis sur Lobste.rs
  • Même s’ils avaient utilisé C++, cela aurait probablement été un choix raisonnable pour Bun. Ils auraient eu des constructeurs et des destructeurs, et auraient pu supprimer beaucoup de code wrapper extern "C", mais ils auraient quand même dû s’appuyer sur un guide de style imposé par la revue de code, et même avec ASAN, il y aurait toujours eu des corruptions mémoire et des fuites.
    Ce qui est amusant, c’est que Node.js fonctionne très bien en C++, mais je n’ai jamais vraiment considéré Bun comme un projet sérieux. Maintenant, il ressemble à un banc d’essai pour le département marketing d’Anthropic, donc je compte continuer à m’en tenir éloigné.
    • Node a clairement aussi eu des CVE de sûreté mémoire issues de son propre code de bibliothèque. Une simple recherche sur « nodejs memory cves » en fait immédiatement ressortir plusieurs.
    • Dire que Node fonctionne bien en C++, c’est du même ordre que dire que Bun fonctionnait bien en Zig. Node reçoit plus d’attention et a plus d’utilisateurs, donc les problèmes apparaissent plus vite ; au final, les deux sont dans une situation où « tout marche parfaitement si l’on fait extrêmement attention et que personne ne se trompe ».
      Spoiler : en réalité, on ne fait pas autant attention, et on se trompe.
  • La phrase selon laquelle « environ 4 % du code Rust de Bun se trouve dans des blocs unsafe, et 78 % de ceux-ci ne font qu’une ligne » semble formulée pour rassurer, mais le fait qu’un bloc unsafe fasse une ligne ou non n’a pas d’importance. Si l’on y casse les garanties de sûreté, tout le code en dehors du bloc peut lui aussi potentiellement devenir non sound.
    La fusion initiale du port Rust de Bun contenait une unsoundness évidente de ce genre : https://github.com/oven-sh/bun/issues/30719
    Le problème a été pris en compte par les mainteneurs en activant l’outil Miri de Rust dans la CI, et la section « What’s Next » de l’article mentionne aussi Miri (which runs for a growing chunk of code in CI), donc il est encourageant de voir qu’ils travaillent dans cette direction.
    Pour être juste, même du Rust comportant des violations de sûreté peut être plus facile à maintenir selon la qualité du code Zig qu’il remplace. Mais le nombre de lignes par bloc unsafe n’est pas un indicateur de qualité, surtout si ces blocs ne sont pas accompagnés d’autres pratiques de codage, d’expertise ou de vérifications automatisées.
    • Cette phrase ne semble pas tant destinée à rassurer qu’à dire que les blocs unsafe ne viennent pas du processus de portage, mais plutôt des exigences du projet. Si l’on appelle une bibliothèque C, les blocs unsafe sont nécessaires, et on ne peut pas les faire disparaître par un simple refactoring.
      Bien sûr, ce serait possible en réécrivant aussi cette bibliothèque C, mais cela pourra éventuellement être envisagé plus tard.
  • L’un des aspects les plus intéressants de Bun, c’est que c’est essentiellement l’histoire d’un seul ingénieur qui a accompli beaucoup plus que ce à quoi on pouvait s’attendre. Au début, Jarred disait que c’était grâce à Zig, cette fois il dit que c’est grâce à Claude, mais il ne serait pas absurde de dire que c’est grâce à Jarred lui-même ou à son éthique de travail. C’est peut-être aussi la puissance d’une petite équipe, ou le fait de réécrire et réimplémenter des choses qui existaient déjà.
    Dans l’annonce « Bun is joining Anthropic », Jarred disait qu’ils recruteraient davantage d’ingénieurs pour travailler sur Bun, mais à en juger par GitHub, l’équipe Bun semble plutôt avoir diminué. Je ne sais pas trop quelle conclusion en tirer, sinon que « Bun est une petite équipe ».
  • Ils ont dit que « les réécritures sont une idée terrible », puis ont aussitôt procédé à une réécriture.
    La méthode elle-même est intéressante, mais l’article se lit comme un papier marketing. Il manque aussi une analyse du coût, il n’y a pas vraiment de risques associés à la réécriture en Rust, et l’explication concrète de la raison pour laquelle cette réécriture a eu lieu est faible. On pourrait supposer que c’est à cause de la politique no ai de Zig, ou bien d’une volonté interne d’Anthropic de se concentrer sur Rust.
    • Concernant le coût, ce passage semble au moins pertinent : « avant la fusion, 5,9 milliards de tokens d’entrée non mis en cache, 690 millions de tokens de sortie et 72 milliards de lectures de tokens d’entrée mis en cache ont été utilisés, soit environ 165 000 dollars au tarif API ».
      Et sur la raison de la réécriture, je trouve que l’article raconte une histoire assez cohérente. Bun continuait de crasher malgré les mesures prises pour traquer les problèmes, et les développeurs voulaient une manière plus systématique d’empêcher ce genre de problèmes.
      Le plan initial consistait à imposer plus strictement un certain style de codage et à introduire des smart pointers, mais Jared estimait que leurs smart pointers maison seraient moins ergonomiques que Rust et n’apporteraient pas de garanties. C’est ainsi que l’idée est devenue : « et si on testait pendant une semaine si le nouveau modèle d’Anthropic peut réécrire Bun en Rust ? ». À mesure que le taux de réussite de la suite de tests augmentait, on semble être passé de « ça vaut peut-être le coup » à « on va fusionner ».
      Autrement dit, il ne semble pas qu’ils aient décidé dès le départ de réécrire en Rust ; cela ressemble plutôt à : « Rust semble apporter une solution au problème, mais le coût d’une réécriture nous en empêche. Or un portage par LLM montre que c’est peut-être faisable. Donc allons vers une réécriture par LLM ».
    • « Avant la fusion, 5,9 milliards de tokens d’entrée non mis en cache, 690 millions de tokens de sortie et 72 milliards de lectures de tokens d’entrée mis en cache ont été utilisés, soit environ 165 000 dollars au tarif API. »
    • Les raisons de la réécriture semblent suffisamment justifiées. Il y avait des problèmes persistants de sûreté mémoire et la crainte d’une chasse aux bugs sans fin façon tape-taupe ; certains ingénieurs auraient répondu autrement, mais choisir une réécriture en Rust avec un LLM est aussi une solution au problème.
    • Ça se lit clairement comme un article marketing. Que ce soit intentionnel ou non, mais en réalité le calcul du coût est presque impossible, surtout si les développeurs avaient accès aux modèles d’Anthropic avant leur sortie. Cela dit, si nous, gens ordinaires, devions réécrire quelque chose de cette ampleur avec les modèles Claude actuels, le coût pourrait être estimé.
  • Plutôt que de corriger ces bugs un par un indéfiniment, vouloir faire mieux pour les utilisateurs qui dépendent de Bun et empêcher systématiquement que ces bugs se répètent est une excellente raison. Mais même si la raison avait été « Rust a une bonne vibe », je l’aurais accepté.
    Félicitations à Jarred, à l’équipe Bun et à Anthropic pour avoir réussi cela.