2 Punkte von GN⁺ 3 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Bun, das mit Zig begann, ist zu einer Runtime mit über 22 Millionen Downloads pro Monat herangewachsen; wiederholte Stabilitätsprobleme durch das Zusammenspiel einer GC-basierten JavaScript-Engine mit manueller Speicherverwaltung wurden jedoch zum Auslöser für den Wechsel zu Rust
  • Statt 535.496 Zeilen Zig-Code ein Jahr lang manuell zu migrieren, liefen in Claude Code etwa 50 dynamische Workflows und bis zu 64 Claude-Instanzen 11 Tage lang parallel
  • Das Porting wurde mit PORTING.md, LIFETIMES.tsv, einem Implementierer und mindestens zwei adversarialen Reviewern sowie der bestehenden TypeScript-Testsuite validiert und bestand auf CI für 6 Plattformen zu 100 %
  • Nach dem Wechsel zu Rust behebt Bun v1.4.0 128 Bugs, die sich in v1.3.14 reproduzieren lassen, beseitigt alle instrumentierbaren Memory Leaks und reduziert die Binärgröße unter Linux und Windows um rund 20 %
  • Bun v1.3.14 ist die letzte Zig-Version, v1.4.0 die erste Rust-Version und wird als Canary bereitgestellt; das Team nutzt Borrow Checker, Miri, LeakSanitizer und 24/7 coverage-basiertes Fuzzing als Werkzeuge zur Verbesserung der Stabilität

Bun begann mit Zig und hatte Stabilitätsprobleme

  • Bun startete als Projekt, das den JavaScript-/TypeScript-Transpiler von esbuild aus Go zeilenweise portierte
  • Der erste Zig-Code wurde am 16. April 2021 geschrieben, und Zigs Low-Level-Kontrolle sowie performanceorientiertes Design ermöglichten die frühe Implementierung von Bun
  • Das frühe Bun wurde ein Jahr lang von einer einzelnen Person in Zig entwickelt und hatte einen sehr großen Umfang
    • JavaScript-, TypeScript- und CSS-Transpiler, -Minifier und -Bundler
    • npm-kompatibler Paketmanager
    • Jest-ähnlicher Test Runner
    • Node.js- und TypeScript-kompatible Modulauflösung
    • HTTP/1.1- und WebSocket-Clients
    • Implementierungen von Node.js-APIs wie fs, net und tls
  • Heute wird die Bun-CLI über 22 Millionen Mal pro Monat heruntergeladen, Claude Code und OpenCode verwenden sie als Runtime, und Vercel, Railway und DigitalOcean bieten 1st-party-Support

Wiederholte Bugs bei der Speichersicherheit

  • Beispiele für in Bun v1.3.14 behobene Bugs umfassen use-after-free, double-free, Memory Leaks, out-of-bounds-Zugriffe und Race Conditions
    • heap-use-after-free durch Aufruf von .reset() während eines asynchronen .write() in node:zlib
    • use-after-free in node:http2, bei dem ein reentranter JS-Callback ein Rehashing der Hashmap auslöst und dadurch interne Stream-Pointer ungültig werden
    • Problem in UDPSocket.send() und sendMany(), bei dem Callbacks von valueOf() oder toString() ein ArrayBuffer detachen
    • Crashes und out-of-bounds-Reads in Buffer#copy und Buffer#fill, wenn während der Argument-Coercion ein ArrayBuffer detached oder resized wird
    • Memory Leaks im Zusammenhang mit crypto.scrypt, tlsSocket.setSession() und fs.watch()
    • double-free bei der Verarbeitung von Vendor Prefixes und Multi-Layer-Backgrounds im CSS-Parser
    • Crash durch Race Condition bei MessageEvent während gleichzeitiger Zugriffe auf BroadcastChannel oder MessagePort
  • Bereits zuvor wurden verschiedene Maßnahmen zur Verbesserung der Stabilität eingesetzt
    • Unterstützung für Address Sanitizer in den Zig-Compiler gepatcht und die ASAN-Testsuite bei jedem Commit ausgeführt
    • Unter Windows Auslieferung von Zig-Safety-checked-ReleaseSafe-Builds
    • 24/7-Fuzzing der Bun-Runtime-API mit Fuzzilli
    • Betrieb zahlreicher End-to-End-Tests auf Memory Leaks
  • Die Position ist nicht, dass Zig selbst das Problem sei; die Anforderung, GC-Werte und manuell verwalteten Speicher gemeinsam zu handhaben, war die Hauptquelle der Stabilitätsprobleme

Warum Rust gewählt wurde

  • JavaScript ist eine GC-Sprache, und Engines wie JavaScriptCore und V8 haben strenge Regeln für Exception Handling und Garbage Collection
  • Zig verwaltet Speicher wie C nicht automatisch, hat keine Konstruktoren oder Destruktoren, und Cleanup muss meist an jedem Call Site explizit mit defer angegeben werden
  • Bei Bun war die korrekte Behandlung der Lifetimes von GC-Werten und manuell verwalteten Werten eine der größten Ursachen für Stabilitätsprobleme
    • Es muss geprüft werden, wo allokierte Bytes wieder freigegeben werden
    • Es muss garantiert werden, dass nur einmal freigegeben wird
    • JavaScript-Exception-Handling muss korrekt geprüft werden
    • Es muss sichergestellt werden, dass GC-Pointer für den conservative stack scanner sichtbar sind
  • Zigs Cleanup-Mechanismen sind explizite defer und errdefer; C++ nutzt Destruktoren und Move-Semantik, Rust verwendet Drop
  • Im bestehenden Zig-Code von Bun gab es eine Mischung aus Arena-Lifetimes, Reference Counting und sorgfältigem Review
  • Ownership-Regeln lassen sich zwar mit Styleguide und Code-Review erzwingen, aber in sicherem Rust werden use-after-free, double-free und fehlende free-Aufrufe auf Error Paths zu Compilerfehlern
  • Etwa 20 % des Bun-Codes sind C++, zudem werden mehrere C-/C++-Bibliotheken eingebettet
    • JavaScriptCore
    • uWebSockets und usockets
    • lshpack und lsquic
    • BoringSSL
    • SQLite
  • Auch C++ wäre eine Option gewesen, würde aber weiterhin auf Styleguides und Code-Review angewiesen bleiben, und selbst mit ASAN können Speicherbeschädigungen und Leaks auftreten

Strategie für das Rewrite: auf einmal und mechanisch

  • Der bestehende Zig-Code von Bun umfasste ohne Kommentare 535.496 Zeilen, und ein traditionelles Rewrite wurde als Arbeit für ein kleines Engineering-Team über etwa ein Jahr eingeschätzt
  • Da Bugfixes, Sicherheitsupdates und Feature-Entwicklung nicht für ein Jahr gestoppt werden konnten, wurde ein mechanisches Porting mit minimalen Änderungen am Nutzerverhalten als risikoärmster Ansatz gewählt
  • Die Testsuite von Bun war in TypeScript geschrieben und hing nicht von der Implementierungssprache der Runtime ab
  • Ein inkrementelles Rewrite wurde als kurzfristig und mittelfristig schmerzhaft bewertet, weil dafür temporärer Code nötig wäre, der später wieder gelöscht werden müsste; deshalb wurde alles auf einmal migriert
  • Der Rust-Code wurde so geschrieben, dass er wie aus dem Zig-Code transpiliert wirkt; nach Bun v1.4 soll dann schrittweise unsafe reduziert und in idiomatisches Rust refaktoriert werden

Dynamische Workflows in Claude Code

  • Für das Rust-Rewrite liefen in Claude Code etwa 50 dynamische Workflows über 11 Tage hinweg durchgehend
  • Die Workflows reichten vom Schreiben eines Porting-Guides über Dateikonvertierung, das Beheben von Compilerfehlern, das Wiederherstellen von Subcommands, das Bestehen der vollständigen Testsuite bis hin zu groß angelegtem Cleanup
    • Erstellen eines Porting-Guides, der Zig-Muster und -Typen auf Rust-Muster und -Typen abbildet
    • Mechanisches Portieren aller .zig-Dateien zu .rs-Dateien gemäß PORTING.md und LIFETIMES.tsv
    • Beheben von Compilerfehlern pro Crate
    • Wiederherstellen des Verhaltens von Subcommands wie bun test und bun build
    • Bestehen der vollständigen Testsuite
    • Groß angelegte Refaktorierung und Cleanup
  • Während des Großteils der Zeit las ein Mensch die Workflow-Ausgaben, prüfte Probleme und Bugs und passte die Prompts an, damit Claude die Schleifen korrigiert
  • Als Vorarbeit wurde etwa drei Stunden lang mit Claude besprochen, wie sich Muster aus der Zig-Codebasis auf Rust abbilden lassen; das Ergebnis wurde in PORTING.md serialisiert
  • Um dem manuell verwalteten Speicher-Code Rust-Lifetimes hinzuzufügen, wurde ein Workflow ausgeführt, der die Lifetimes aller Struct-Felder analysiert
    • Auffinden von Feldern mit komplexen Lifetimes
    • Vorschlagen von Lifetimes
    • Review durch zwei adversariale Review-Agents
    • Übernahme des Feedbacks und Speicherung in LIFETIMES.tsv

Adversariales Review-Verfahren

  • Für jede Implementierung wurde in einem separaten Context Window von Claude ein adversarialer Reviewer Claude eingesetzt; der Reviewer erhielt nur den Diff und wurde angewiesen, davon auszugehen, dass der Code falsch ist, und Bugs zu finden.
  • Die Grundstruktur bestand aus 1 Implementierer, mindestens 2 adversarialen Reviewern und 1 Fixer.
  • Die Bugs, die die Reviewer tatsächlich fanden, kompilierten zwar alle, hatten aber Probleme im Laufzeitverhalten.
    • uv_close ist asynchron, aber Box<uv::Pipe> wurde am Ende des Match-Arms gedroppt, wodurch libuv auf bereits freigegebenen Speicher zeigte; das führte zu Use-after-free und Double-free.
    • Bei negativer, nicht ganzzahliger File Time erzeugte die Verwendung von trunc() einen negativen nsec, ein timespec-Fehler.
    • unwrap_or wertet sein Argument eager aus, wodurch im Fall einer ausgelassenen Percentage bei color-mix() ein Panic ausgelöst wurde.
  • Wie bei menschlichen Reviews wurden auch hier der Context von Autor und Reviewer getrennt, um Bias zu verringern, der daraus entstehen kann, dass der Implementierer mergen möchte.

Durchführung des großangelegten Portings und Parallelisierung

  • Bevor alle 1.448 .zig-Dateien portiert wurden, wurde das Verfahren zunächst mit 3 Dateien validiert.
    • 1 Implementierer schrieb die .rs-Datei.
    • 2 Reviewer prüften, ob sie mit .zig und dem Verhalten übereinstimmt und PORTING.md sowie LIFETIMES.tsv einhält.
    • 1 Fixer übernahm die Vorschläge.
  • Zu Beginn des vollständigen File-Portings führten mehrere Claude-Instanzen git stash, git stash pop und git reset HEAD --hard aus und gerieten dadurch gegenseitig in Konflikt.
  • Danach wurden dem Workflow Regeln hinzugefügt, die git stash, git reset, git-Befehle außer dem Committen bestimmter Dateien sowie langsame Befehle wie cargo verboten.
  • Am Ende wurden 4 Workflow-Shards und 4 Worktrees verwendet, und in jedem Shard commiteten und pushten 16 Claude-Instanzen Dateien.
  • Dank Parallelisierung und Vorbereitung schrieb Claude in der Spitze etwa 1.300 Zeilen Code pro Minute.
  • Der Port-Branch hatte 6.502 Commits ohne Merge-Commits, die Peak-Stunde lag bei 695 Commits, und der final gelandete Diff umfasste +1.009.272 Zeilen.
  • Da die Standard-IOPS der EC2-Instanz nicht erhöht worden waren, konnte schon ein langsames grep dazu führen, dass Disk-Reads und -Writes für mehrere Minuten stillstanden.

Compilerfehler und Aufteilung in Crates

  • Nachdem der gesamte Code geschrieben war, behebt der Claude-Workflow Compilerfehler.
  • Die Zig-Codebasis war faktisch eine einzige Compilation Unit, während der Rust-Code für schnellere Builds in etwa 100 Crates aufgeteilt werden sollte.
  • Die schwierigste Fehlerkategorie waren zyklische Abhängigkeiten.
    • Allein der PR zur Crate-Aufteilung direkt vor dem Rust-Rewrite reichte nicht aus.
    • Ein separater Workflow klassifizierte und dokumentierte, wo Code mit zyklischen Abhängigkeiten untergebracht werden sollte.
    • Ein weiterer Workflow führte das entsprechende Refactoring durch.
  • Nach dem Auflösen der zyklischen Abhängigkeiten wurden rund 16.000 Compilerfehler sichtbar.
  • Diese Fehler wurden pro Crate parallel bearbeitet.
    • In jeder Crate wurde cargo check ausgeführt.
    • Die Ausgabe wurde nach Datei gruppiert und gespeichert.
    • Die Compilerfehler der jeweiligen Crate wurden behoben.
    • 2 adversariale Reviewer prüften die Änderungen.
    • 1 Fixer übernahm die Korrekturen.
  • Es gab auch einen Fehlstart, bei dem Claude „alle Crates kompilierbar machen“ als Aufforderung verstand, Function Stubs zu erzeugen.
  • Als sich ein Muster entwickelte, Workarounds mit langen erklärenden Kommentaren zu rechtfertigen, wurde als Review-Regel ergänzt: „Wenn ein absatzlanger Kommentar nötig ist, ist der Code falsch und der Code muss repariert werden.“

Der Weg bis zum Bestehen der Tests

  • Nachdem cargo check durchlief, wurden der Reihe nach Linkerfehler, Panics direkt nach dem Start, bun --version und die Ausführung von bun test <file> behoben.
  • Für Fehlschläge nach CLI-Subcommand wurden Stacktraces in Dateien gespeichert und mit einem Workflow aus Implementierer-, Reviewer- und Fixer-Schleife korrigiert.
  • Der Testdatei-Workflow shardete etwa 100 zufällige Testdateien auf 4 Worktrees, speicherte Stacktraces und Fehler pro Fehlschlag und behob sie dann.
  • Die Test-Suite enthielt Memory-Leak-Tests und Integrationstests, die in Debug-Builds in Timeouts laufen konnten.
    • Ein Test, der next dev ausführt und prüft, ob Hot Module Reloading 100 Änderungen erkennt
    • Ein Stresstest, der die maximale Zahl an TCP-Sockets ausschöpft
    • Ein Test für Disk-Reads und -Writes im Gigabyte-Bereich
    • Ein Test, der rund 10.000 Prozesse spawnt
  • Zur Isolation wurden mit systemd-run und cgroups Speicher- und CPU-Nutzung begrenzt und der PID-Namespace getrennt.
  • Trotzdem stürzte die Maschine mehrfach wegen fehlendem Speicherplatz auf der Festplatte ab.
  • Zwei Tage nach dem ersten CI-Lauf war die Zahl der fehlschlagenden Testdateien von 972 auf 23 gesunken, und anderthalb Tage später war Linux vollständig grün.
  • Am Ende liefen die kompletten CI-Tests auf 6 Plattformen durch.
    • macOS x64
    • macOS arm64
    • Linux x64
    • Linux arm64
    • Windows x64
    • Windows arm64
  • Nach 100 % bestandenen Tests prüften Menschen manuell, dass die Tests tatsächlich ausgeführt und nicht übersprungen wurden, und führten dann den Merge durch.
  • Der Merge nach main war kein versioniertes Release; es bestand noch nicht genug Vertrauen für ein Release, aber genug Vertrauen, um sich vollständig auf das Rewrite zu konzentrieren.

Testumfang und Kosten

  • In 11 Tagen, vom 3. Mai bis zum Merge am 14. Mai, entstanden 6.778 Commits.
  • Es wurden keine Tests gelöscht oder übersprungen.
  • Der Testumfang pro Plattform war wie folgt.
    • Debian 13 x64: expect() 1.386.826-mal, 60.624 Tests, 4.174 Dateien
    • macOS 14 arm64: expect() 1.259.953-mal, 58.850 Tests, 4.175 Dateien
    • Windows 2019 x64: expect() 1.007.544-mal, 57.337 Tests, 4.173 Dateien
  • Für die Arbeiten vor dem Merge wurden 5,9 Milliarden uncached Input Tokens, 690 Millionen Output Tokens und 72 Milliarden gelesene cached Input Tokens verbraucht.
  • Zu API-Listenpreisen lagen die Kosten bei rund 165.000 US-Dollar.
  • Die Einschätzung lautet, dass 3 Ingenieure mit Context über die gesamte Codebasis von Hand ungefähr 1 Jahr gebraucht hätten.
  • Als verwendetes Modell wird das Pre-Release-Claude Fable 5 genannt, und Bun enthält die Disclosure, dass es im Dezember 2025 von Anthropic übernommen wurde.

Security-Review, Fuzzing und Stand von unsafe

  • Nach dem Merge des Rust-Ports wurden mit Claude Code Security 11 Runden Security-Review abgeschlossen und die Findings bearbeitet.
  • Für alle Parser von Bun wurde 24/7 Coverage-guided Fuzzing hinzugefügt.
    • JavaScript
    • TypeScript
    • JSX
    • CSS
    • JSON5
    • JSONC
    • TOML
    • YAML
    • Markdown
    • INI
    • Bun-Shell-Skripte
    • semver-Bereiche
    • .patch-Dateien
    • CSS-Farben
  • Der Fuzzer sendet gefundene Bugs an Claude, damit dieser reproduzierbare PRs inklusive Fix einreicht; Menschen reviewen dann den PR.
  • Bislang wurden die Parser 100 Milliarden Mal ausgeführt, was zu etwa 15 PRs führte.
  • Zum Zeitpunkt des Schreibens befinden sich etwa 4 % des Rust-Codes innerhalb von unsafe-Blöcken.
    • etwa 13.000 unsafe-Keywords
    • etwa 27.000 Zeilen / insgesamt rund 780.000 Zeilen
    • 78 % der unsafe-Blöcke sind einzeilig und betreffen Pointer aus C++ oder Aufrufe von C-Bibliotheken
  • Da weiterhin C/C++-Bibliotheken wie JavaScriptCore verwendet werden, werde es immer mehr unsafe geben als in einem reinen Rust-Projekt.

Nach der Rust-Umstellung entdeckte Regressionen

  • Das Rust-Rewrite war eine große Änderung und verursachte 19 bekannte Regressionen, die inzwischen alle behoben wurden.
  • Die meisten entstanden durch Code, dessen Syntax in beiden Sprachen ähnlich ist, dessen Bedeutung sich aber unterscheidet.
  • Side Effects in debug_assert!

    • Zigs assert ist eine Funktion, daher werden die Argumente in allen Builds ausgeführt.
    • Rusts debug_assert! ist ein Makro, daher wird im Release-Build der gesamte Ausdruck entfernt.
    • Der Aufruf von insert_stale verschwand im Release-Build, wodurch ein bestimmter HMR-Fall in HTML-Route-Projekten mit React kaputtging.
    • Zugehöriges Issue: #30678
  • Slice mit ungerader Länge

    • Buns Zig-Helper reinterpretSlice(u16, bytes) verwendete @divTrunc und ignorierte dadurch das nachlaufende ungerade Byte.
    • Rusts bytemuck::cast_slice führt bei ungerader Länge zu einem Panic.
    • Es gab eine Regression, bei der Blob.text() mit einem UTF-16-BOM und einem ungeraden Byte danach keinen String zurückgab, sondern den Prozess per Panic beendete.
    • Der Fix bestand darin, das ungerade Byte mit &buf[..buf.len() & !1] wieder zu ignorieren.
    • Zugehöriges Issue: #31188
  • Bounds Checks

    • Der Zig-Code auf macOS und Linux wurde mit ReleaseFast kompiliert, wodurch Bounds Checks entfernt wurden; Rust-Release-Builds behalten Bounds Checks bei.
    • Die Größe des Overflow-Blocks im Bun-Module-Resolver blieb beim Platzhalter 64, wodurch die Obergrenze von 8,4 Millionen interned filenames auf 270.272 sank.
    • Das portierte Off-by-one bei ptrs[4095] wurde dadurch in realen Projekten erreichbar, und Rust löste statt eines Out-of-Bounds-Write einen Panic aus.
    • Zugehöriges Issue: #31503
  • comptime-Format-Strings

    • In Zig ist bei Output.pretty das fmt comptime, daher werden Farbmarker wie <r> und <d> vor der Argumentersetzung in ANSI-Escapes umgewandelt.
    • Die Rust-Funktion hat keinen comptime-Parameter, verarbeitete die Marker daher im fertigen String und schrieb dabei auch die Argumente fälschlich um.
    • Bei bun update -i kollidierten die OSC-8-Hyperlink-Terminierung und der nachgestellte <r>-Marker, sodass r als Text ausgegeben wurde.
    • In Rust war dafür das Makro bun_core::pretty!("<r>{}<r>", hyperlink) nötig.
    • Zugehöriges Issue: #30693

Behobene Bugs und Memory Leaks

  • Bun v1.4.0 behebt 128 Bugs, die sich in v1.3.14 reproduzieren ließen.
  • Das Spektrum reicht von Memory Leaks über Crashes bis hin zu falsch eingefärbtem Hilfetext.
  • Rusts Drop ruft automatisch die Funktion drop auf, wenn ein Wert den Scope verlässt.
  • In Zig musste an jeder Call Site defer hinzugefügt werden, wodurch leicht Cleanup fehlte oder doppelt ausgeführt wurde.
  • Drop ist die Entscheidung, versteckten Control Flow zu akzeptieren, um dafür häufige Footguns zu reduzieren.
  • Drop behebt mehrere Memory Leaks rund um Dateipfade im Error-Handling-Code.
  • Buns Integration mit LeakSanitizer wurde verbessert und verfolgt jetzt alle native code memory allocations.
  • Alle instrumentierbaren Memory Leaks wurden behoben.
  • Verbesserungen bei Leaks in Bun.build()

    • In Bun v1.3.14 überlebten bei jedem In-Process-Aufruf von Bun.build() der geparste Source-Text und die AST-Symboltabelle die Lebensdauer des Builds und verursachten jeweils Leaks von mehreren MB.
    • In einem Test, der dasselbe Projekt mit 60 Modulen 2.000-mal in einem einzigen Prozess bundelte, leakte v1.3.14 pro Build weiterhin etwa 3 MB.
    • In Bun v1.4.0 flacht der Speicherverbrauch ab.
    • | 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 |

Binärgröße, Stack-Nutzung, Performance

  • Allein die ersten Änderungen des Rust-Rewrite verkleinerten bereits die Binärgröße.
    • Windows: 3,8 MB weniger
    • macOS: 5,5 MB weniger
    • Linux: 6,8 MB weniger
  • Der Hauptgrund war, dass im Zig-Code comptime zu häufig verwendet wurde.
  • Danach kamen außerdem Identical Code Folding, das Entfernen ungenutzter Daten aus ICU und das verzögerte Dekomprimieren von Teilen von libicu mit einem zstd dictionary hinzu.
  • Zusammengenommen reduzieren Rust-Rewrite, ICU-Änderungen und Identical Code Folding die Größe des Bun-Binaries unter Linux und Windows um etwa 20 %.
Version Plattform Größe
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
  • Der TOML-Parser und Buns Recursive-Descent-Parser benötigen nun weniger Stack-Speicher.
  • Rusts LLVM-IR-Codegen gibt für Stack-Variablen die Intrinsics llvm.lifetime.start und llvm.lifetime.end aus, sodass LLVM Stack-Slots wiederverwenden kann.
  • Zuvor wurden besonders große Funktionen manuell in mehrere kleinere Funktionen refaktoriert, um ein offenes Issue in Zig zu umgehen.
  • Rust unterstützt Cross-Language Link-Time Optimization zwischen C/C++ und Rust, wodurch sprachübergreifendes Inlining möglich wird.
  • Linux-x64-Benchmarks

    • Bun v1.3.14 und Bun v1.4.0 wurden auf Linux x64 auf einem EC2 Xeon Platinum 8488C verglichen.
    • Der HTTP-Durchsatz wurde mit oha, die App-Workloads mit hyperfine gemessen.
    • | 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% |

Praxisbeispiele und Release-Status

  • Prisma hat die Public Beta von Prisma Compute auf Basis von Buns Rust-Rewrite veröffentlicht
  • Laut Prisma wurden im Rust-Rewrite ein Connection Pool, der sich nach VM-Pause/Resume nicht erholt, sowie ein Memory-Leak-Failure-Mode getestet, und diese Failure Modes seien gut behandelt worden
  • Claude Code v2.1.181 und Versionen seit dem Release vom 17. Juni verwenden den auf Rust portierten Bun
  • Der Start von Claude Code unter Linux wurde um 10 % beschleunigt, ansonsten hätten die meisten Nutzer den Unterschied kaum bemerkt
  • Bun v1.3.14 ist die letzte in Zig geschriebene Bun-Version
  • Bun v1.4.0 ist die erste in Rust geschriebene Bun-Version und wird als Canary bereitgestellt

Werkzeuge, die das Team gewonnen hat, und verbleibende Arbeit

  • Die neue Rust-Codebasis behält eine Form bei, die der bestehenden Zig-Codebasis sehr ähnlich ist
  • Sie wurde so geschrieben, dass Personen, die den ursprünglichen Zig-Code verstehen, auch den mechanisch übersetzten Rust-Code verstehen können
  • Das Review der Rust-Rewrite-PRs erfolgte so, dass ein adversarial Review-Agent prüfte, ob Inkonsistenzen zwischen Zig und Rust sowie die Einhaltung des Porting Guide und des Lifetime Guide korrekt erkannt werden, während Menschen viel Code Side-by-Side lasen
  • Bun v1.4 macht Bun schneller und kleiner, senkt den Speicherverbrauch und liefert Werkzeuge für Verbesserungen bei der Stabilität
    • Rust Borrow Checker
    • Miri
    • LeakSanitizer
    • 24/7 coverage-guided Fuzzing für den Parser
  • Es gibt noch Teile, die refaktoriert werden müssen; bun-unsafe-audit ist verlinkt
  • Ein einzelner Engineer überwachte Fable und Claude Code genau und erreichte in nur 11 Tagen den Zustand, dass die gesamte Test-Suite auf allen Plattformen erfolgreich durchläuft

1 Kommentare

 
GN⁺ 3 시간 전
Lobste.rs-Kommentare
  • Selbst wenn man C++ verwendet hätte, wäre das für Bun vermutlich eine vernünftige Wahl gewesen. Man hätte Konstruktoren und Destruktoren bekommen und auch viel extern "C"-Wrapper-Code löschen können, wäre aber weiterhin auf einen per Code-Review erzwungenen Styleguide angewiesen gewesen, und selbst mit ASAN hätte es weiter Speicherkorruption und Speicherlecks gegeben.
    Lustigerweise läuft Node.js auch mit C++ gut, aber ich habe Bun nie als ernsthaftes Projekt betrachtet. Inzwischen wirkt es eher wie eine Testbench für die Marketingabteilung von Anthropic, daher werde ich weiter Abstand davon halten.
    • Auch bei Node gab es eindeutig Memory-Safety-CVEs, die aus dem eigenen Bibliothekscode stammten. Schon eine Suche nach "nodejs memory cves" liefert sofort mehrere Treffer.
    • Dass Node mit C++ gut läuft, ist ungefähr genauso aussagekräftig wie zu sagen, Bun habe mit Zig gut funktioniert. Node bekommt nur mehr Aufmerksamkeit und hat mehr Nutzer, daher treten Probleme schneller zutage; am Ende befinden sich beide in einer Lage nach dem Motto: „Es funktioniert perfekt, solange alle extrem vorsichtig sind und niemand Fehler macht.“
      Spoiler: In der Praxis sind die Leute nicht so vorsichtig, und Fehler passieren trotzdem.
  • Die Aussage „Etwa 4 % des Rust-Codes von Bun befinden sich in unsafe-Blöcken, und 78 % davon sind einzeilig“ klingt wie ein Versuch zu beruhigen, aber ob ein unsafe-Block nur eine Zeile hat, ist nicht wichtig. Wenn darin die Sicherheitsgarantien verletzt werden, kann dadurch potenziell die Soundness des gesamten Codes außerhalb des Blocks mit beschädigt werden.
    Im frühen Merge des Rust-Ports von Bun war genau so eine offensichtliche Unsoundness enthalten: https://github.com/oven-sh/bun/issues/30719
    Auf das Issue haben die Maintainer reagiert, indem sie Rusts Miri-Tool in der CI aktiviert haben, und da im Abschnitt „What's Next“ des Artikels auch Miri (which runs for a growing chunk of code in CI) steht, scheint man in diese Richtung weiterzuarbeiten, was gut aussieht.
    Fairerweise kann selbst Rust mit Sicherheitsverletzungen trotzdem wartbarer sein als der ersetzte Zig-Code, je nachdem, wie dessen Qualität war. Aber die Anzahl der Codezeilen pro unsafe-Block ist kein Qualitätsmaßstab, insbesondere dann nicht, wenn es in diesen Blöcken auch sonst an anderen Coding-Praktiken, Expertise oder automatisierten Prüfungen fehlte.
    • Der Satz soll meiner Meinung nach nicht beruhigen, sondern eher ausdrücken, dass die unsafe-Blöcke nicht durch den Portierungsprozess entstanden sind, sondern aus den Projektanforderungen resultieren. Wenn man C-Bibliotheken aufruft, braucht man unsafe-Blöcke, und die lassen sich nicht einfach durch Refactoring entfernen.
      Natürlich ginge das, wenn man auch diese C-Bibliotheken neu schreiben würde, aber das könnte man gegebenenfalls später prüfen.
  • Einer der interessantesten Aspekte an Bun ist, dass es im Kern die Geschichte davon ist, wie ein einzelner Ingenieur viel mehr geschafft hat als erwartet. Jarred sagte anfangs, das liege an Zig, und jetzt sei es Claude zu verdanken, aber es wäre genauso plausibel zu sagen, dass es an Jarred selbst oder seiner Arbeitsethik liegt. Vielleicht ist es auch die Stärke eines kleinen Teams oder der Umstand, dass hier bereits existierende Dinge neu geschrieben und implementiert werden.
    In der Ankündigung „Bun is joining Anthropic“ sagte Jarred, dass für die Arbeit an Bun mehr Ingenieure eingestellt würden, aber wenn man sich nur GitHub ansieht, scheint das Bun-Team eher kleiner geworden zu sein. Ich weiß nicht genau, welche Schlussfolgerung man daraus ziehen sollte, außer vielleicht: „Bun ist ein kleines Team.“
  • Man sagt „Rewrites sind eine schreckliche Idee“ und führt dann direkt einen Rewrite durch.
    Die Methode selbst ist interessant, aber der Artikel liest sich wie ein Marketingstück. Es fehlt an einer Analyse, wie hoch die Kosten tatsächlich waren, die Risiken eines Rust-Rewrites werden nicht behandelt, und auch die konkrete Erklärung, warum es überhaupt zu dem Rewrite kam, bleibt schwach. Meine Vermutung wäre, dass es am no ai policy von Zig liegt oder dass es intern bei Anthropic eine Strategie gab, sich stärker auf Rust zu konzentrieren.
    • Was die Kosten angeht, wirkt diese Stelle zumindest einschlägig: „Bis zum Merge wurden 5,9 Milliarden nicht gecachte Input-Tokens, 690 Millionen Output-Tokens und 72 Milliarden gecachte Input-Token-Lesevorgänge verbraucht, was zu API-Listenpreisen etwa 165.000 US-Dollar entsprach.“
      Und warum der Rewrite stattfand, dazu erzählt der Artikel meines Erachtens eine ziemlich konsistente Geschichte. Bun hatte Maßnahmen ergriffen, um die Probleme in den Griff zu bekommen, stürzte aber weiterhin ab, und die Entwickler wollten eine systematischere Methode, um solche Probleme zu verhindern.
      Der ursprüngliche Plan war offenbar, einen bestimmten Coding-Stil strenger durchzusetzen und Smart Pointer einzuführen, aber Jared hielt eigene Smart Pointer für schlechter benutzbar als Rust und ohne vergleichbare Garantien. Daraus wurde dann wohl: „Was wäre, wenn wir eine Woche lang testen, ob das neue Modell von Anthropic Bun nach Rust umschreiben kann?“ Als dann immer mehr Tests bestanden, wechselte die Einschätzung von „könnte machbar sein“ zu „wir mergen das“.
      Es wirkt also weniger so, als habe man sich von Anfang an für einen Rust-Rewrite entschieden, sondern eher wie: „Rust scheint die Lösung für das Problem zu sein, aber wegen der Kosten eines Rewrites geht das nicht. Doch mit einer LLM-Portierung sieht es plötzlich machbar aus. Dann machen wir eben den LLM-Rewrite.“
    • „Bis zum Merge wurden 5,9 Milliarden nicht gecachte Input-Tokens, 690 Millionen Output-Tokens und 72 Milliarden gecachte Input-Token-Lesevorgänge verbraucht, was zu API-Listenpreisen etwa 165.000 US-Dollar entsprach.“
    • Der Grund für den Rewrite wirkt hinreichend gerechtfertigt. Es gab anhaltende Memory-Safety-Probleme und die Angst vor endlosen Bugfixes nach dem Maulwurfspiel-Prinzip; manche Ingenieure hätten vielleicht anders reagiert, aber einen Rust-Rewrite per LLM zu wählen, ist ebenfalls ein Ansatz zur Problemlösung.
    • Es liest sich definitiv wie ein Marketingartikel. Ob beabsichtigt oder nicht: Die tatsächlichen Kosten lassen sich praktisch kaum berechnen. Das gilt erst recht, wenn die Entwickler schon vor der Veröffentlichung Zugriff auf die Anthropic-Modelle hatten. Man kann aber zumindest abschätzen, was es kosten würde, wenn normale Leute wie wir mit den aktuellen Claude-Modellen heute etwas in dieser Größenordnung erneut schreiben ließen.
  • Statt diese Bugs immer nur einzeln zu beheben, den abhängigen Nutzern gegenüber bessere Arbeit leisten zu wollen und systematisch zu verhindern, dass sich solche Fehler wiederholen, ist ein hervorragender Grund. Aber selbst wenn der Grund einfach nur gewesen wäre, dass Rust gerade gute Stimmung hat, hätte ich das wohl akzeptiert.
    Glückwunsch an Jarred, das Bun-Team und Anthropic, dass sie das geschafft haben.