Frontier · Bun Blog

Reescrevendo Bun em Rust

Jarred Sumner explica por que e como o bundler de Bun foi reescrito de Zig para Rust, incluindo os loops de codificação de agente, revisão contraditória, testes e trabalho de desempenho por trás da migração.

Este artigo foi traduzido do texto original em inglês.

Edição em texto: figuras de dados, visualizações interativas e diagramas de fluxo da fonte não são reproduzidos aqui. Consulte o original para ver os gráficos completos.

Divulgação: Bun foi adquirido por Anthropic em dezembro de 2025. Eu e outros membros da equipe Bun trabalhamos em Anthropic. Usei uma versão de pré-lançamento de Claude Fable 5 para grande parte da reescrita de Rust.

Bun começou como uma conversão linha por linha, de Go para Zig, do transpilador JavaScript e TypeScript do esbuild. Escrevi minha primeira linha de Zig em 16 de abril de 2021. Apostei em Zig depois de ver a Zig Language Reference de página única no Hacker News e fiquei muito entusiasmado com o controle de baixo nível e o cuidado com o desempenho.

Desde o início, o escopo do Bun era enorme:

  • JavaScript, TypeScript e transpilador, minificador e empacotador CSS
  • gerenciador de pacotes compatível com npm
  • Jest
  • Resolução de módulo compatível com Node.js e TypeScript
  • Cliente HTTP/1.1 e WebSocket
  • Implementações de API Node.js como fs, net, tls e dezenas de outros módulos

A versão inicial de Bun foi escrita por mim em 1 ano, em um apartamento apertado em Oakland, pré-LLM, em Zig. O resultado padrão para projetos com escopo ambicioso como Bun é entrar no cemitério de projetos secundários em uma página de perfil do GitHub. Zig tornou Bun possível. Eu nunca teria conseguido construir tanto em 1 ano se não fosse por Zig.

Atualmente, a CLI do Bun recebe mais de 22 milhões de downloads mensais. Ferramentas populares como Claude Code e OpenCode apostam em Bun como tempo de execução. Vercel, Railway, DigitalOcean e outras têm suporte próprio para Bun.

Bun também tem sido um desafio para a estabilidade. Aqui está uma pequena amostra de bugs que corrigimos no Bun v1.3.14:

  • Falha no heap-use-after-free em node:zlib ao chamar .reset() em um fluxo zlib, Brotli ou Zstd enquanto um assíncrono .write() ainda está em andamento no threadpool
  • Falha de uso após liberação em node:zlib quando um retorno de chamada onerror emitiu uma reentrada write() seguida por close() em identificadores nativos
  • O uso após livre trava em node:http2 quando retornos de chamada JS reentrantes (por exemplo, session.request() dentro de um ouvinte de tempo limite, um getter de opções ou um retorno de chamada de gravação) acionam um rehash de mapa de hash, invalidando ponteiros de fluxo internos
  • use-after-free em UDPSocket.send() e sendMany() onde o código do usuário em retornos de chamada valueOf() ou toString() poderia separar um ArrayBuffer entre a captura da carga útil e o envio real
  • travamento e leitura fora dos limites em Buffer#copy e Buffer#fill quando um retorno de chamada valueOf desconecta ou redimensiona o ArrayBuffer subjacente durante a coerção do argumento
  • gravação fora dos limites do heap em UDPSocket.sendMany() quando o estado da conexão do soquete mudou no meio da iteração por meio de retornos de chamada JS do usuário
  • vazamento de memória em crypto.scrypt onde o retorno de chamada e os buffers protegidos por senha/salt nunca foram liberados quando a alocação do buffer de saída falhou
  • SSLWrapper.init vazou a senha do strdup em caminhos de erro
  • vazamento de memória em tlsSocket.setSession() onde cada chamada vazou um SSL_SESSION (~6,5 KB por chamada) devido à falta de SSL_SESSION_free após d2i_SSL_SESSION
  • vazamento de memória onde os observadores fs.watch() nunca foram coletados como lixo depois de .close(), causado por um estouro de contagem de referência que fixou permanentemente cada observador como uma raiz do GC
  • Falha dupla no analisador CSS quando background-clip tinha prefixos de fornecedor e planos de fundo multicamadas
  • DuplexUpgradeContext nunca foi liberado — um vazamento completo por tls.connect({ socket: duplex })
  • falha na condição de corrida em MessageEvent onde o encadeamento do marcador GC pode observar uma variante interrompida em m_data durante o acesso simultâneo de um BroadcastChannel ou MessagePort

Poderíamos ter continuado corrigindo esses tipos de bugs uma única vez para sempre, mas devemos isso aos nossos usuários que contam conosco para fazer melhor do que isso e evitar sistematicamente que esses tipos de bugs se repitam.

O que já estávamos fazendo

  • Corrigimos o compilador Zig para adicionar suporte ao Address Sanitizer. Executamos nosso conjunto de testes com ASAN em cada commit.
  • Enviamos Zig versões ReleaseSafe com segurança verificada no Windows
  • Nós difundimos as APIs de tempo de execução do Bun 24 horas por dia, 7 dias por semana, usando Fuzzilli, o difusor do mecanismo JavaScript usado por V8 e JavaScriptCore
  • Temos vários testes completos de vazamento de memória

Isso é mais do que muitos projetos fazem.

Seja realmente inteligente e não cometa erros?

Nossa lista de correções de bugs parecia ruim e eu estava cansado de dormir preocupado com travamentos em Bun. Eu não culpo Zig por isso - outros usuários de Zig não têm os bugs que tivemos, e misturar GC com memória gerenciada manualmente é algo incomum o suficiente para o software precisar que nenhuma linguagem realmente o projete. Não teríamos chegado tão longe se não fosse por Zig, e sempre serei grato. Até muito recentemente, a escolha da linguagem de programação era uma decisão unilateral para um projeto como Bun.

JavaScript é uma linguagem de coleta de lixo e mecanismos JavaScript modernos como JavaScriptCore (e V8) têm regras rígidas sobre o tratamento de exceções e o coletor de lixo. Zig, como C, não gerencia memória para você e esta é uma compensação que para muitos projetos é um ótimo motivo para usar Zig. Zig não possui construtores/destruidores, e espera-se que a maior parte da limpeza seja escrita explicitamente em cada site de chamada com defer.

Para Bun, lidar corretamente com os tempos de vida dos valores coletados como lixo e dos valores gerenciados manualmente tem sido uma grande fonte de problemas de estabilidade - na maioria das vezes, pequenos vazamentos de memória e, ocasionalmente, travamentos. Cada alocação de memória deve ser revisada meticulosamente. Onde esses bytes são liberados? Como podemos garantir que ele seja liberado apenas uma vez? Verificamos as exceções JavaScript corretamente? Este ponteiro coletado como lixo é visível para o scanner de pilha conservador? Essa memória é coletada como lixo ou gerenciada manualmente?

Para questões de estabilidade, é melhor saber o mais cedo possível. A difusão acontece depois que o código é mesclado. CI acontece quando o código é enviado. As verificações de segurança em tempo de execução e a limpeza de endereço acontecem quando o código é executado (esperançosamente em desenvolvimento, antes da CI).

Uma maneira comum de reduzir esse tipo de problema é garantir que o código de limpeza seja sempre executado exatamente uma vez para o código que precisa dele. Zig foi projetado para ser uma linguagem simples, sem fluxo de controle oculto e, portanto, prefere a palavra-chave explícita defer para executar código no final de um escopo em vez do ~Destructor implícito do C++ ou do Rust implícito Drop.

IdiomaLimpeza
Zigdefer, errdefer
C++~Destructor, && move
RustDrop

Para código Zig, quando exatamente devemos executar o código de limpeza? Se passarmos o mesmo *T para muitas funções diferentes, como saberemos quando ele não estará mais acessível e poderá ser limpo? Como funciona quando algumas funções precisam continuar referenciando a memória após a função ser chamada? Nossa abordagem atual é uma mistura de:

  • vida útil da arena, onde o escopo de quando está acessível é claro (o estado do analisador não escapa da função de chamada e, portanto, os nós AST são uma boa escolha nesse caso)
  • contagem de referências
  • preste muita atenção

Muitos projetos optam por responder a esse tipo de pergunta por meio de um guia de estilo. O TigerStyle de TigerBeetle é um exemplo em Zig e o guia de estilo C++ de 31.000 palavras do Google é outro. O desafio dos guias de estilo é a aplicação. Como você garante que o guia de estilo seja seguido? Historicamente, a revisão de código era a resposta com a aplicação do melhor esforço por meio de linters e analisadores estáticos.

Ter um guia de estilo rígido com expectativas de propriedade claras e explicitamente definidas no sistema de tipos era uma opção real para Bun. Como Zig não tem sobrecarga de operador, provavelmente acabaríamos com muito código parecido com isto:

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();

  // ...
}

Isso é menos ergonômico do que o Zig que esperamos:

fn foo(a: *TCPSocket) !void {
  const b = try do_something_with_a(a);
  // ...
}

E quanto a C/C++?

Cerca de 20% do código do Bun é escrito em C++ e Bun incorpora diversas bibliotecas C/C++:

  • JavaScriptCore, o mecanismo JavaScript que alimenta o Safari
  • uWebSockets e usockets - nosso servidor HTTP/WebSocket e loop de eventos
  • lshpack e lsquic - HPACK e bibliotecas HTTP/3
  • BoringSSL, o fork do OpenSSL do Google
  • SQLite

C++ em vez de Zig seria uma escolha razoável para Bun. Teríamos construtores e destruidores. Poderíamos excluir muitos códigos de wrapper extern "C".

Mas ainda dependeríamos de guias de estilo aplicados por meio de revisão de código e, mesmo com ASAN, corrupção de memória e vazamentos de memória ainda ocorreriam.

Por que Rust?

Uma grande porcentagem de bugs dessa lista são de uso após liberação, liberação dupla e "esqueci de liberar" em um caminho de erro. Em Rust seguro, estes são erros de compilador e limpeza automática semelhante a RAII com Drop. Erros do compilador são um ciclo de feedback melhor do que um guia de estilo.

Historicamente, reescrever é uma péssima ideia. Excluindo comentários, Bun equivale a 535.496 linhas de Zig. Uma reescrita em outro idioma levaria um ano inteiro para uma pequena equipe de engenheiros. Isso significaria congelar correções de bugs, correções de segurança ou desenvolvimento de recursos naquele momento. A abordagem menos arriscada para conseguir algo entregável seria uma conversão mecânica de Zig para Rust, com o número mínimo de mudanças comportamentais, usando exatamente o mesmo conjunto de testes que já usamos para testar Bun.

Felizmente, o próprio conjunto de testes do Bun é escrito em TypeScript, o que significa que não depende da linguagem de programação do tempo de execução.

Um ano de impacto zero para os usuários não é uma opção realista que poderíamos considerar. Portanto, a aplicação por meio de estilo de código para corrigir problemas de estabilidade foi nossa melhor aposta e foi nosso plano quando adicionamos ponteiros inteligentes inspirados em Rust à base de código de Bun.

Mas, honestamente, eu não queria fazer isso. Ponteiros inteligentes caseiros oferecem pior ergonomia do que Rust, sem nenhuma das garantias.

E se, em vez disso, eu passar uma semana testando se o novo modelo de Anthropic pode reescrever Bun em Rust?

No início, não esperava que funcionasse. Alguns dias depois, uma grande porcentagem do conjunto de testes começou a passar e vi o quanto o novo código Rust correspondia à base de código Zig original. Minha opinião passou de "vale a pena tentar" para "vou mesclar isso".

Claude, reescreva Bun em Rust.

Há muitas maneiras de fazer um péssimo trabalho nisso. Por exemplo, solicitando a Claude "Reescreva Bun em Rust. Não cometa erros." e então rezar para que funcionasse não foi o que eu fiz.

Pense em como uma pessoa faria isso. A primeira grande questão é:

Reescrita incremental? Ou tudo de uma vez?

Na minha experiência ao portar o transpiler do esbuild de Go para Zig para a versão inicial de Bun (sem LLMs), tudo de uma vez é melhor. Uma reescrita incremental adiciona código temporário que você espera que seja excluído eventualmente e que seria doloroso a curto e médio prazo.

A segunda grande questão: como?

Como mantemos Bun em Rust o mesmo Bun de antes, com a mesma arquitetura, desempenho e conjunto de recursos e, ao mesmo tempo, obtendo os recursos de linguagem de Rust como o verificador de empréstimo? Como podemos garantir que a equipe ainda possa mantê-lo após a reescrita?

Faça a reescrita que parece que transpilamos nosso código Zig para Rust. Podemos refatorá-lo gradualmente para reduzir o uso de unsafe e parecer mais idiomático com Rust após o lançamento do Bun v1.4.

Essas são as duas únicas grandes questões. Todo o resto são táticas.

Loops que escrevem e revisam código

Muito do trabalho diário de engenharia como engenheiros de software pode ser simplificado demais em loops.

// 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);
}

Um task tem algum contexto associado a ele (um ticket do Jira, um problema do GitHub, etc.). O result é o código que você escreveu para consertar. Revisor(es) de código review as alterações para verificar regressões e correção. E então você responde ao feedback.

Eu reescrevi Bun em Rust usando cerca de 50 fluxos de trabalho dinâmicos em Claude Code executados continuamente ao longo de 11 dias.

Cada fluxo de trabalho dinâmico era um loop como este - um fluxo de trabalho para:

  • Gere um guia de portabilidade mapeando Zig padrões e tipos para Rust padrões e tipos
  • Portar mecanicamente cada arquivo .zig para um arquivo .rs, correspondendo a PORTING.md e LIFETIMES.tsv
  • Corrigir erros do compilador de cada caixa
  • Faça com que subcomandos como bun test ou bun build funcionem
  • Faça todos os testes de todo o conjunto de testes do Bun para passar
  • Vários refatoradores grandes e passagens de limpeza

Durante a maior parte desses 11 dias (e depois), monitorei os fluxos de trabalho, lendo manualmente os resultados para verificar problemas e bugs e solicitando que Claude editasse o loop para corrigir as coisas.

Como você analisa um PR com mais de 1 milhão de linhas adicionadas? Como você começa a construir a confiança necessária para mesclar de forma responsável grandes quantidades de código de autoria LLM?

Um conjunto de testes independente de linguagem com um milhão de afirmações, revisão de código contraditória e, quando algo dá errado, correção do processo que gera o código em vez de correção manual do código.

Revisão adversária

A revisão adversária pede a Claude (em uma janela de contexto separada) que apresente exaustivamente os motivos pelos quais as mudanças criam bugs ou não funcionam.

Dividir janelas de contexto

Normalmente, com humanos, a pessoa que revisa o código não é a pessoa que o criou. A pessoa que escreve o código deseja mesclá-lo, o que pode influenciar suas ações para que ele seja enviado antes que ele esteja pronto.

Claude é da mesma maneira. O Claude que escreveu o código quer que o código seja aceito. O Claude que revisa quer encontrar problemas no código.

1 implementador, 2 ou mais revisores adversários por implementador. A única função do revisor: encontrar bugs e razões pelas quais o código não funciona. O implementador não revisa. O revisor não implementa.

bug 1 de 3 · fechamento assíncrono

seu contexto: o .zig original, o plano portuário, seu próprio raciocínio

seu contexto: apenas o diff. instruído a presumir que o código está errado.

src/runtime/api/bun/js_bun_spawn_bindings.rs · compila limpo

for stdio in [spawned_stdout, spawned_stderr] {
    match stdio {
        StdioResult::Buffer(mut pipe) => {
            // pipe: Box<uv::Pipe> — entregue à libuv para ser fechado
            pipe.close(Subprocess::on_pipe_close)
        }
    }
}

StdioResult::Fd(fd) => fd.close(),

StdioResult::Unavailable => {}

}

}

uv_close é assíncrono: a libuv mantém o ponteiro bruto do identificador até o próximo ciclo do loop e então chama on_pipe_close, que libera a alocação. Mas pipe é um Box descartado ao final desse braço de correspondência; a libuv fica com um ponteiro para memória já liberada e o callback seguinte tenta liberá-la novamente. Isso causa use-after-free e, depois, double-free.

Box::leak(pipe).close(Subprocess::on_pipe_close)

f0a454376c7 · win-review: js_bun_spawn_bindings.rs leak Boxuv::Pipe antes de async uv_close para evitar UAF/double-free em on_pipe_close

Três bugs que os revisores adversários realmente detectaram — cada commit citado traz sua atribuição de revisão na linha de assunto. Todos os três compilados; todos os três pareciam plausíveis. O revisor é um segundo Claude em sua própria janela de contexto: ele obtém a diferença e nada mais - nenhum raciocínio do implementador - e é instruído a descobrir o que está errado. O código é condensado dos commits citados; mesmos bugs, mesmas correções.

Como é isso?

Se você está prestes a fazer algo grande e caro, você economiza tempo e dinheiro se eliminar o risco primeiro.

Trabalho de preparação

Antes de escrever qualquer código, passei cerca de 3 horas conversando com Claude sobre como mapear padrões de nossa base de código Zig de perto para Rust. Claude serializou essa discussão em um documento PORTING.md, que acabou no Hacker News.

A próxima pergunta: como adicionar tempos de vida Rust ao código que gerencia a memória manualmente?

Foi aí que eu sugeri a Claude algo assim:

Eu: Vamos iniciar um fluxo de trabalho dinâmico para analisar os tempos de vida adequados de cada campo struct na base de código. Este fluxo de trabalho deve ler todos os campos de estrutura em cada arquivo e rastrear o fluxo de controle. Primeiro, procure campos struct com tempos de vida complexos para express em Rust, depois proponha um tempo de vida para esse campo, depois use dois agentes de revisão adversários para revisar esse tempo de vida e, em seguida, aplique qualquer feedback e serialize em um LIFETIMES.tsv para outras cláusulas analisarem.

Em seguida, uma rodada de análises adversas sobre PORTING.md e LIFETIMES.tsv juntas para corrigir quaisquer sugestões conflitantes e verificar tudo. Eu também li manualmente.

Execução de teste

Antes de pedir a Claude para traduzir todos os 1.448 arquivos .zig para arquivos .rs, comecei com apenas 3. Para cada um dos 3 arquivos, 1 implementador escreveu o novo arquivo .rs, 2 revisores adversários verificaram se o arquivo .rs correspondia ao comportamento do arquivo .zig e se ele seguia o PORTING.md e LIFETIMES.tsv. Depois disso, 1 fixador aplicou qualquer sugestão.

Inícios falsos

Pedi a Claude para repetir o fluxo de trabalho em todos os 1.448 arquivos .zig e, cerca de 2 minutos depois, Claude executou git stash antes de confirmar. Outro executou git stash pop. E então git reset HEAD --hard. Eles estavam pisando um no outro! E se eu colocasse cada Claude em uma árvore de trabalho separada, ficaria sem espaço em disco porque o repositório git do Bun é muito grande e eventualmente as alterações precisarão ser compiladas e vistas juntas.

Então, pedi ao Claude para editar o fluxo de trabalho para instruí-lo a nunca executar git stash ou git reset ou qualquer comando git que não envie um arquivo específico de uma só vez. Não cargo também. Nenhum comando lento.

Então, Claude retomou os fluxos de trabalho. E estava funcionando! Muito lentamente, então dividi-o em apenas 4 fragmentos de fluxo de trabalho, cada um com sua própria árvore de trabalho (4 árvores de trabalho no total), cada um executando 16 cláusulas, confirmando e enviando arquivos.

Finalmente escrevendo o código

Graças a toda a paralelização e a esse trabalho de preparação, no auge, Claude escreveu cerca de 1.300 linhas de código por minuto. Cada linha de código foi revisada por dois revisores adversários separados (também Claude) e passou por uma rodada de correções antes de ser confirmada. Absolutamente nada disso funcionou ainda.

11 dias × 24 horas · PDT

5.463 confirmações

1.695 commits/hora

12h6h12h18h 4 de maio, 4 de maio, 7h-8h PDT — 6 commits, +89.278 linhas 4 de maio, 8h-9h PDT — 2 commits, +50.742 linhas 4 de maio, 9h-10h PDT — 1 commit, +28.149 linhas 4 de maio, 11h-12h PDT — 1 commit, +39.752 linhas 4 de maio, das 12h às 13h PDT - 3 commits, +251.616 linhas 4 de maio, das 13h às 14h PDT - 2 commits, +161.724 linhas 4 de maio, das 15h às 16h PDT - 3 commits, +136.381 linhas 4 de maio, das 17h às 18h PDT - 5 commits, +895 linhas 4 de maio, 18h às 19h PDT - 5 commits, +17.027 linhas 4 de maio, 19h às 20h PDT - 1 commit, +106 linhas 4 de maio, 21h às 22h PDT - 13 commits, +11.661 linhas 4 de maio, 23h às 12h PDT - 6 commits, +8.516 linhas 5 de maio, 5 de maio, 12h à 1h PDT - 9 commits, +1.381 linhas 5 de maio, 1h às 2h PDT - 7 commits, +1.577 linhas 5 de maio, 2h às 3h PDT - 4 commits, +2.035 linhas 5 de maio, 3h às 4h PDT - 4 commits, +7.808 linhas 5 de maio, 4h às 5h PDT - 1 commit, +2.796 linhas 5 de maio, 5h às 6h PDT - 2 commits, +29.370 linhas 5 de maio, 8h às 9h PDT - 2 commits, +7.076 linhas 5 de maio, 9h às 10h PDT - 2 commits, +308 linhas 5 de maio, 11h às 12h PDT - 2 commits, +1.643 linhas 5 de maio, 12h às 13h PDT - 4 commits, +1.452 linhas 5 de maio, das 13h às 14h PDT — 1 commit, +2.142 linhas 5 de maio, das 14h às 15h PDT — 4 commits, +7.787 linhas 5 de maio, das 15h às 16h PDT — 2 commits, +5.835 linhas 5 de maio, das 16h às 17h PDT — 1 commit, +3.417 linhas 5 de maio, das 17h às 18h PDT — 4 commits, +3.960 linhas 5 de maio, das 18h às 19h PDT - 4 commits, +9.179 linhas 5 de maio, das 19h às 20h PDT - 4 commits, +1.983 linhas 5 de maio, das 20h às 21h PDT - 4 commits, +18.902 linhas 5 de maio, das 21h às 22h PDT - 43 commits, +40.650 linhasMaio 5 de maio, das 22h às 23h PDT - 139 commits, +64.842 linhas 5 de maio, das 23h às 12h PDT - 141 commits, +34.814 linhas 6 de maio, 12h à 1h PDT - 60 commits, +10.417 linhas 6 de maio, 1h às 2h PDT - 296 commits, +38.530 linhas 6 de maio, 2h às 3h PDT - 306 commits, +18.836 linhas 6 de maio, 3h às 4h PDT - 196 commits, +10.245 linhas 6 de maio, 4h às 5h PDT - 86 commits, +2.655 linhas 6 de maio, 5h às 6h PDT - 16 commits, +289 linhas 6 de maio, 8h às 9h PDT - 5 commits, +264 linhas 6 de maio, 9h às 10h PDT - 458 commits, +16.409 linhas 6 de maio, 10h às 11h PDT - 695 commits, +44.000 linhas 6 de maio, 11h às 12h PDT - 102 commits, +21.972 linhas 6 de maio, 12h às 13h PDT - 19 commits, +2.891 linhas 6 de maio, das 13h às 14h PDT — 3 commits, +56 linhas 6 de maio, das 15h às 16h PDT — 64 commits, +3.606 linhas 6 de maio, das 16h às 17h PDT — 264 commits, +60.132 linhas 6 de maio, das 17h às 18h PDT — 268 commits, +40.953 linhasMaio 6 de maio, das 18h às 19h PDT - 281 commits, +16.283 linhas 6 de maio, das 19h às 20h PDT - 258 commits, +26.654 linhas 6 de maio, das 20h às 21h PDT - 327 commits, +16.599 linhas 6 de maio, das 21h às 22h PDT - 74 commits, +8.331 linhas 6 de maio, 22h às 23h PDT - 17 commits, +2.200 linhas 6 de maio, 23h às 12h PDT - 11 commits, +3.590 linhas 7 de maio 7 de maio, 12h à 1h PDT - 17 commits, +6.577 linhas 7 de maio, 1h às 2h PDT - 22 commits, +8.718 linhas 7 de maio, 2h às 3h PDT - 21 commits, +11.392 linhas 7 de maio, 3h às 4h PDT - 53 commits, +6.476 linhas 7 de maio, 4h às 5h PDT - 31 commits, +2.356 linhas 7 de maio, 5h às 6h PDT - 9 commits, +1.787 linhas 7 de maio, 6h às 7h PDT - 4 commits, +580 linhasMaio 7 de maio, das 7h às 8h PDT - 5 commits, +181 linhas 7 de maio, das 11h às 12h PDT - 3 commits, +421 linhas 7 de maio, das 12h às 13h PDT - 1 commit, +13 linhas 7 de maio, das 13h às 12h PDT - 5 commits, +248 linhas 7 de maio, das 14h às 15h PDT - 9 commits, +2.131 linhasMaio 7 de maio, das 15h às 16h PDT - 51 commits, +3.207 linhas 7 de maio, das 16h às 17h PDT - 56 commits, +2.647 linhas 7 de maio, das 17h às 18h PDT - 159 commits, +2.787 linhas 7 de maio, das 18h às 19h PDT - 42 commits, +1.590 linhas 7 de maio, das 19h às 20h PDT - 46 commits, +4.170 linhas 7 de maio, 20h às 21h PDT - 52 commits, +2.113 linhas 7 de maio, 21h às 22h PDT - 27 commits, +1.585 linhas 7 de maio, 22h às 23h PDT - 27 commits, +2.231 linhas 7 de maio, 23h às 12h PDT - 30 commits, +4.987 linhas 8 de maio, 8 de maio, 12h às 1h PDT - 27 commits, +1.196 linhas 8 de maio, 1h às 2h PDT - 14 commits, +904 linhas 8 de maio, 2h às 3h PDT - 8 commits, +536 linhas 8 de maio, 3h às 4h PDT - 13 commits, +253 linhas 8 de maio, 4h às 5h PDT - 3 commits, +771 linhas 8 de maio, 5h às 6h PDT - 15 commits, +1.545 linhas 8 de maio, 6h às 7h PDT - 12 commits, +1.965 linhas 8 de maio, 7h às 8h PDT - 14 commits, +1.866 linhas 8 de maio, 8h às 9h PDT - 55 commits, +3.622 linhasMaio 8 de maio, das 9h às 10h PDT — 35 commits, +4.778 linhas 8 de maio, das 10h às 11h PDT — 1 commit, +0 linhas 8 de maio, das 12h às 13h PDT — 1 commit, +116 linhas 8 de maio, das 13h às 14h PDT — 2 commits, +66 linhas 8 de maio, das 14h às 15h PDT — 9 commits, +1.071 linhas 8 de maio, das 15h às 16h PDT - 26 commits, +1.691 linhas 8 de maio, das 16h às 17h PDT - 18 commits, +2.751 linhas 8 de maio, das 17h às 18h PDT - 2 commits, +97 linhas 8 de maio, das 18h às 19h PDT - 2 commits, +135 linhas 8 de maio, das 19h às 20h PDT - 11 commits, +1.763 linhas 8 de maio, das 20h às 21h PDT — 20 commits, +5.272 linhas 8 de maio, das 21h às 22h PDT — 12 commits, +952 linhas 8 de maio, das 22h às 23h PDT — 2 commits, +334 linhas 8 de maio, das 23h às 12h PDT — 6 commits, +2.033 linhas 9 de maio, 9 de maio, 12h-1h PDT - 9 commits, +387 linhas 9 de maio, 1h-2h PDT - 9 commits, +723 linhas 9 de maio, 2h-3h PDT - 8 commits, +98 linhas 9 de maio, 3h-4h PDT - 63 commits, +2.538 linhas 9 de maio, 4h-5h PDT - 11 commits, +8.861 linhasMaio 9 de maio, 5h às 6h PDT - 4 commits, +42 linhas 9 de maio, 6h às 7h PDT - 3 commits, +2.616 linhas 9 de maio, 7h às 8h PDT - 6 commits, +6.993 linhas 9 de maio, 8h às 9h PDT - 1 commit, +3.705 linhas 9 de maio, 9h às 10h PDT - 11 commits, +199 linhas 9 de maio, das 11h às 12h PDT — 1 commit, +23 linhas 9 de maio, das 12h às 13h PDT — 4 commits, +5.012 linhas 9 de maio, das 13h às 14h PDT — 7 commits, +2.080 linhas 9 de maio, das 12h às 15h PDT — 6 commits, +924 linhas 9 de maio, das 15h às 16h PDT — 5 commits, +248 linhas 9 de maio, das 16h às 17h PDT - 17 commits, +508 linhas 9 de maio, das 17h às 18h PDT - 2 commits, +135 linhas 9 de maio, das 18h às 19h PDT - 4 commits, +822 linhas 9 de maio, das 19h às 20h PDT - 1 commit, +7 linhas 10 de maio, 10 de maio, das 12h à 1h PDT - 4 commits, +497 linhas 10 de maio, 1h às 2h PDT - 2 commits, +35 linhas 10 de maio, 2h às 3h PDT - 1 commit, +131 linhas 10 de maio, 3h às 4h PDT - 2 commits, +322 linhas 10 de maio, 4h às 5h PDT - 1 commit, +3 linhas 10 de maio, 5h às 6h PDT - 1 commit, +26 linhas 10 de maio, 6h às 7h PDT - 2 commits, +81 linhas 10 de maio, 7h às 8h PDT - 1 commit, +5 linhas 10 de maio, 8h às 9h PDT - 4 commits, +78 linhas 10 de maio, 9h às 10h PDT - 1 commit, +1 linhas 10 de maio, 10h às 11h PDT - 2 commits, +128 linhas 10 de maio, 11h às 12h PDT - 1 commit, +4 linhas 10 de maio, 12h às 13h PDT - 2 commits, +413 linhas 10 de maio, 13h às 14h PDT - 1 commit, +25 linhas 10 de maio, 14h às 15h PDT - 5 commits, +327 linhas 10 de maio, 15h às 16h PDT - 6 commits, +1.172 linhas 10 de maio, das 16h às 17h PDT — 4 commits, +752 linhas 10 de maio, das 17h às 18h PDT — 3 commits, +227 linhas 10 de maio, das 18h às 19h PDT — 2 commits, +242 linhas 10 de maio, das 19h às 20h PDT — 1 commit, +306 linhas 10 de maio, das 20h às 21h PDT — 1 commit, +54 linhas 10 de maio, 21h às 22h PDT - 2 commits, +75 linhas 10 de maio, 22h às 23h PDT - 1 commit, +134 linhas 10 de maio, 23h às 12h PDT - 5 commits, +103 linhas 11 de maio 11 de maio, 12h à 1h PDT - 2 commits, +150 linhas 11 de maio, 1h às 2h PDT - 4 commits, +398 linhas 11 de maio, 2h às 3h PDT - 2 commits, +364 linhas 11 de maio, 3h às 4h PDT - 3 commits, +44 linhas 11 de maio, 4h às 5h PDT - 7 commits, +9.367 linhas 11 de maio, 6h às 7h PDT - 2 commits, +43 linhasMaio 11, 7h às 8h PDT — 2 commits, +149 linhas 11 de maio, 8h às 9h PDT — 10 commits, +2.171 linhas 11 de maio, 9h às 10h PDT — 16 commits, +2.047 linhas 11 de maio, 10h às 11h PDT — 18 commits, +3.356 linhas 11 de maio, 11h às 12h PDT - 9 commits, +861 linhas 11 de maio, 12h às 13h PDT - 3 commits, +412 linhas 11 de maio, 13h às 14h PDT - 12 commits, +2.978 linhas 11 de maio, 14h às 15h PDT - 157 commits, +10.700 linhas 11 de maio, 15h às 16h PDT - 16 commits, +1.346 linhas 11 de maio, das 16h às 17h PDT — 3 commits, +78 linhas 11 de maio, das 17h às 18h PDT — 41 commits, +2.568 linhas 11 de maio, das 18h às 19h PDT — 55 commits, +4.912 linhas 11 de maio, das 19h às 20h PDT — 53 commits, +3.475 linhasMaio 11 de maio, das 20h às 21h PDT — 32 commits, +1.732 linhas 11 de maio, das 21h às 22h PDT — 46 commits, +4.506 linhas 11 de maio, das 22h às 23h PDT — 45 commits, +1.711 linhas 11 de maio, das 23h às 12h PDT — 52 commits, +10.850 linhas 12 de maio 12 de maio, 12h – 1h PDT – 30 commits, +3.760 linhas 12 de maio, 1h – 2h PDT – 24 commits, +9.443 linhas 12 de maio, 2h – 3h PDT – 41 commits, +1.635 linhas 12 de maio, 3h – 4h PDT – 39 commits, +788 linhas 12 de maio, 4h – 5h PDT - 27 commits, +651 linhas 12 de maio, 5h às 6h PDT - 23 commits, +779 linhas 12 de maio, 6h às 7h PDT - 1 commit, +137.576 linhas 12 de maio, 7h às 8h PDT - 2 commits, +81 linhas 12 de maio, 8h às 9h PDT - 2 commits, +75 linhas 12 de maio, 9h às 10h PDT - 2 commits, +130 linhas 12 de maio, 10h às 11h PDT - 5 commits, +160 linhas 12 de maio, 11h às 12h PDT - 2 commits, +20 linhas 12 de maio, 12h às 13h PDT - 1 commit, +2 linhas 12 de maio, 13h às 14h PDT - 30 commits, +2.677 linhas 12 de maio, das 14h às 15h PDT — 41 commits, +7.022 linhas 12 de maio, das 15h às 16h PDT — 4 commits, +200 linhas 12 de maio, das 16h às 17h PDT — 27 commits, +1.423 linhas 12 de maio, das 17h às 18h PDT — 19 commits, +1.055 linhas 12 de maio, 18h às 19h PDT - 2 commits, +380 linhas 12 de maio, 19h às 20h PDT - 2 commits, +84 linhas 12 de maio, 21h às 22h PDT - 7 commits, +273 linhas 12 de maio, 22h às 23h PDT - 3 commits, +230 linhas 12 de maio, 23h às 12h PDT - 7 commits, +319 linhas 13 de maio 13 de maio, 12h às 1h PDT - 2 commits, +133 linhas 13 de maio, 1h às 2h PDT - 14 commits, +2.177 linhas 13 de maio, 2h às 3h PDT - 12 commits, +685 linhas 13 de maio, 4h às 5h PDT - 10 commits, +657 linhas 13 de maio, 5h às 6h PDT - 1 commit, +687 linhas 13 de maio, 6h às 7h PDT - 11 commits, +380 linhas 13 de maio, 7h às 8h PDT - 12 commits, +5.247 linhas 13 de maio, 8h às 9h PDT - 14 commits, +1.051 linhas 13 de maio, 9h às 10h PDT - 7 commits, +680 linhasMaio 13 de maio, das 10h às 11h PDT — 10 commits, +412 linhas 13 de maio, das 11h às 12h PDT — 6 commits, +314 linhas 13 de maio, das 12h às 13h PDT — 10 commits, +2.980 linhas 13 de maio, das 13h às 14h PDT — 1 commit, +0 linhas 13 de maio, das 14h às 15h PDT — 3 commits, +439 linhas 13 de maio, 17h às 18h PDT — 7 commits, +114 linhas 13 de maio, 18h às 19h PDT — 4 commits, +605 linhas 13 de maio, 21h às 22h PDT — 1 commit, +13 linhas 13 de maio, 22h às 23h PDT — 1 commit, +48 linhas 13 de maio, 23h às 12h PDT — 1 commit, +8 linhas 14 de maio 14 de maio, 12h à 1h PDT — 1 commit, +150 linhas

Cada commit na ramificação portuária (exceto mesclagens), agrupados por hora. Horário de pico: 695 commits.

Notou o tempo inconsistente? Esqueci de aumentar o IOPS padrão na instância do EC2 em que foi executado. Um comando grep lento foi suficiente para congelar leituras e gravações de disco por minutos.

Erros do compilador como fila de trabalho

Depois de escrever todo o código, pedi ao Claude para escrever um fluxo de trabalho corrigindo todos os erros do compilador. Fomos caixa por caixa.

≈15.125 erros restantes

Quarta, 6 de maio, 1h29 PDT

errors.txt88 corrigir confirmações

erro: src/event_loop/SpawnSyncEventLoop.rs

erro: src/sys/lib.rs

erro: runtime/timer/TimerObjectInternals.rs

erro: src/runtime/ffi/ffi_body.rs

erro: src/http/lib.rs

erro: src/js_parser/ast/Parser.rs

erro: JSPromise.rs

erro: autônomo_graph/StandaloneModuleGraph.rs

erro: src/http_jsc/websocket_client.rs

erro: doStep5.rs

erro: src/install/PackageManager.rs

dividido · 64 cláusulas

árvore de trabalho 1

→→

→→

→→

→→

árvore de trabalho 2

→→

→→

→→

→→

árvore de trabalho 3

→→

→→

→→

→→

árvore de trabalho 4

→→

→→

→→

→→

1 correção2 revisão1 se aplica

→ compromete terreno por caixa

bun_bundle0

bun_js_parser17

bun_css10

bolo_http3

bun_sys3

bun_alloc1

bun_sourcemap3

bun_ini0

bun_analytics1

bun_zlib0

· phase-d(sql_jsc/mysql/protocol): corrige importações, limites de ReaderContext, auxiliar WTFStringImpl

· phase-d(tier0): bun_sourcemap parse_json: campo BabyList.len, Option<StoreRef> desembrulhando

· phase-d(jpa): skipTypescript.rs — porta corpos reais, elimina _draft/todo stubs

Como a fase D funcionou, reproduzida a partir de seus 1.610 commits reais (6 de maio, PDT): cargo check gravou ≈16.000 erros em um arquivo, agrupados por caixa; o fluxo de trabalho os dividiu entre 64 Claudes – 16 loops em 4 árvores de trabalho, cada um Claude corrigindo, dois revisando e um aplicando. Cada ficha é um lote de commits reais: ela cai em sua caixa real e só então os contadores se movem. Linhas de erro são assuntos reais de commit.

A classe de erro mais complicada são as dependências cíclicas.

Nossa base de código Zig era uma unidade de compilação (efetivamente uma caixa). Eu queria dividir a nova base de código Rust em ~100 caixas para que o Rust compilasse mais rápido, mas isso precisava evitar dependências cíclicas e, ao mesmo tempo, minimizar as alterações em comparação com a implementação Zig original. Meu PR para fazer isso imediatamente antes de iniciar a reescrita de Rust foi insuficiente. Em vez de começar de novo, executei outro fluxo de trabalho para classificar onde o código com dependências cíclicas deveria ir e anotei tudo - e depois outro fluxo de trabalho para fazer a refatoração.

A correção das dependências cíclicas revelou cerca de 16.000 erros do compilador. Um número enorme para 1 humano, mas não um número absurdo para 64 claudes de uma vez.

Para maximizar o paralelismo, o fluxo de trabalho passou por cada caixa.

  • Para cada caixa, execute cargo check, agrupe a saída por arquivo e salve os erros em um arquivo
  • Corrigir todos os erros do compilador nessa caixa
  • 2 revisores adversários para as mudanças na caixa
  • 1 fixador aplica as correções

Para evitar que os claudes pisem uns nos outros, cargo check só correu logo no início e, como as outras corridas, não git até o final.

Outro falso começo

Claude interpretou "vamos compilar todas as caixas" como "estalhar as funções com erros de compilação". Claude também começou a adicionar comentários explicativos suspeitosamente longos para documentar soluções alternativas, então adicionei esta regra para os revisores adversários rejeitarem:

Se você precisar de um comentário de um parágrafo para justificar por que a solução alternativa está correta, o código está errado – corrija o código.

Uma edição imediata e algumas horas depois, essas coisas pararam de acontecer.

Testes de fumaça

As modelos adoram dizer "testes de fumaça"

Depois que cargo check for aprovado, o próximo passo será compilar e executar bun --version. Tinha erros de vinculador. Então, ele entrou em pânico imediatamente ao iniciar.

O próximo objetivo era fazê-lo funcionar bun test <file>. Assim que funcionasse, poderíamos começar a fazer testes! É hora de outro fluxo de trabalho, repetindo os subcomandos Bun CLI:

  • Salve cada stacktrace com falha em um arquivo junto com seu subcomando
  • Para cada stacktrace com falha agrupado por subcomando, tenha 1 correção de Claude
  • 2 revisores adversários
  • 1 corretor aplica as sugestões

Faça com que o conjunto de testes seja aprovado localmente

Este fluxo de trabalho foi repetido em arquivos de teste.

Execute cerca de 100 arquivos de teste aleatórios fragmentados em uma das 4 árvores de trabalho por pasta na base de código. Para cada teste com falha, salve o stacktrace e os erros em um arquivo, 1 implementador propõe uma correção, 2 revisores adversários e, em seguida, 1 corretor se aplica.

Ainda mais inícios falsos

Nosso conjunto de testes tem muitos testes de vazamento de memória e alguns testes de integração que podem levar mais de um minuto - por exemplo: um teste que executa next dev e verifica o recarregamento do módulo a quente pode detectar alterações 100 vezes. Vários desses testes atingem o tempo limite em compilações de depuração.

Também temos testes de estresse que esgotam o número máximo de soquetes TCP na máquina, testes que leem e gravam gigabytes no disco e testes que geram ~10 mil processos.

Isso precisava de um isolamento mais forte do que "por favor", então usamos systemd-run (cgroups) para limitar o uso de memória e CPU e isolar namespaces pid. A máquina ficou sem espaço em disco e travou várias vezes mesmo assim.

Faça com que o conjunto de testes seja aprovado no CI

Dois dias após a primeira execução do CI, a lista de falhas caiu de 972 arquivos de teste para 23. Um dia e meio depois disso, o Linux ficou totalmente verde — e pela primeira vez, parecia que essa reescrita do Rust iria realmente funcionar.

0/6 plataformas verdes

build #53047 · Sábado, 9 de maio, 11h52 PDT

macOS x64 · 2 fragmentos

compilação nº 52897: build de falhas de shard nº 52932: build de falhas de shard nº 52934: build de falhas de shard nº 52938: build de falhas de shard nº 52944: build de falhas de shard nº 52946: build de falhas de shard nº 52949: build de falhas de shard nº 52975: build de falhas de shard nº 52998: build de falhas de shard #53007: build de falhas de shard #53015: build de falhas de shard #53026: build de falhas de shard #53027: build de falhas de shard #53035: build de falhas de shard #53041: build de falhas de shard #53047: build de falhas de shard #53056: build de falhas de shard #53077: build de falhas de shard #53090: build de falhas de shard #53095: build de falhas de shard #53106: build de falhas de shard #53109: build de falhas de shard #53123: build de falhas de shard #53127: build de falhas de shard #53130: build de falhas de shard #53131: build de falhas de shard #53133: shard failedbuild #53134: shard failedbuild #53143: shard failedbuild #53149: todos os shards PassBuild #53159: todos os shards PassBuild #53164: todos os shards PassBuild #53167: todos os shards PassBuild #53172: todos os shards PassBuild #53176: todos os shards PassBuild #53194: todos os shards PassBuild #53208: todos os shards PassBuild #53213: shard FailuresBuild #53214: shard FailuresBuild #53216: sem falhas (execução parcial) build #53222: todos os shards PassBuild #53229: sem falhas (execução parcial) Build #53241: todos os shards PassBuild #53265: todos os shards passaram na compilação #53271: todos os shards passaram na compilação #53304: shard failedbuild #53327: shard failedbuild #53340: shard failedbuild #53401: shard failedbuild #53431: shard failedbuild #53491: todos os shards passaram na build #53503: todos shards PassBuild #53748: shard FailuresBuild #53753: todos os shards PassBuild #53787: todos os shards PassBuild #53811: todos os shards PassBuild #53933: sem falhas (execução parcial)Build #53952: todos os shards PassBuild #53983: shard FailuresBuild #53992: shard FailuresBuild #53999: shard FailuresBuild #54012: todos os shards PassBuild #54015: todos os shards PassBuild #54017: todos os shards PassBuild #54022: shard FailuresBuild #54026: shard FailuresBuild #54033: todos os shards PassBuild #54040: todos os shards PassBuild #54047: shard FailuresBuild #54049: shard FailuresBuild #54057: shard FailuresBuild #54064: todos os shards PassBuild #54074: todos os shards PassBuild #54093: todos os shards PassBuild #54144: sem falhas (execução parcial)Build #54161: shard FailuresBuild #54186: shard FailuresBuild #54189: falha no shardbuild #54196: falha no shardbuild #54202: todos os shards foram aprovados

Linux arm64 · 60 fragmentos

compilação nº 52934: build de falhas de shard nº 52938: build de falhas de shard nº 52944: build de falhas de shard nº 52969: build de falhas de shard nº 52975: build de falhas de shard nº 52980: build de falhas de shard nº 52988: build de falhas de shard nº 52996: build de falhas de shard nº 52998: shard failedbuild #53007: shard failedbuild #53013: shard failedbuild #53014: shard failedbuild #53015: shard failedbuild #53026: shard failedbuild #53027: shard failedbuild #53031: sem falhas (execução parcial)build #53032: shard failedbuild #53035: build de falhas de shard #53041: build de falhas de shard #53047: build de falhas de shard #53056: build de falhas de shard #53059: build de falhas de shard #53077: build de falhas de shard #53083: build de falhas de shard #53086: build de falhas de shard #53090: build de falhas de shard #53095: build de falhas de shard #53106: build de falhas de shard #53109: build de falhas de shard #53123: build de falhas de shard #53127: build de falhas de shard #53130: build de falhas de shard #53131: build de falhas de shard #53133: build de falhas de shard #53134: build de falhas de shard #53135: shard failedbuild #53143: shard failedbuild #53149: shard failedbuild #53159: shard failedbuild #53164: shard failedbuild #53167: todos os shards passarambuild #53172: shard failedbuild #53176: todos os shards passarambuild #53188: shard Failuresbuild #53194: shard failedbuild #53208: todos os shards PassBuild #53212: shard FailuresBuild #53213: shard FailuresBuild #53214: shard FailuresBuild #53216: todos os shards PassBuild #53222: todos os shards PassBuild #53229: todos os shards PassBuild #53236: sem falhas (execução parcial) build # 53241: todos os shards passaram build # 53260: todos os shards passaram build # 53265: todos os shards passaram build # 53271: todos os shards passaram build # 53280: sem falhas (execução parcial) build # 53298: sem falhas (execução parcial) build # 53304: falhas de shard build # 53327: todos shards PassBuild #53340: todos os shards PassBuild #53360: nenhuma falha (execução parcial)Build #53419: shard FailuresBuild #53431: shard FailuresBuild #53458: nenhuma falha (execução parcial)Build #53485: shard FailuresBuild #53491: shard FailuresBuild #53503: shard FailuresBuild #53514: sem falhas (execução parcial)build #53570: shard failedbuild #53583: shard failedbuild #53599: shard failedbuild #53748: shard failedbuild #53753: todos os shards passarambuild #53762: sem falhas (execução parcial)build #53787: sem falhas (execução parcial)build #53811: todos os shards PassBuild #53852: sem falhas (execução parcial)Build #53863: shard FailuresBuild #53893: shard FailuresBuild #53914: todos os shards PassBuild #53933: todos os shards PassBuild #53952: todos os shards PassBuild #53983: shard FailuresBuild #53992: shard failedbuild #53999: shard failedbuild #54008: sem falhas (execução parcial)build #54012: todos os shards PassBuild #54015: todos os shards PassBuild #54017: todos os shards PassBuild #54022: shard FailuresBuild #54026: todos os shards PassBuild #54030: shard FailuresBuild #54033: todos os shards passaram na compilação #54040: todos os shards passaram na compilação #54047: shard failedbuild #54049: shard failedbuild #54055: shard failedbuild #54057: shard failedbuild #54064: todos os shards passaram na compilação #54074: todos os shards passaram na build #54083: sem falhas (execução parcial)build #54093: todos os shards foram aprovadosbuild #54144: todos os shards foram aprovadosbuild #54161: shard failedbuild #54186: shard failedbuild #54189: shard failedbuild #54196: shard failedbuild #54202: todos os shards foram aprovados

Linux x64 · 60 fragmentos

compilação nº 52934: build de falhas de shard nº 52938: build de falhas de shard nº 52944: build de falhas de shard nº 52969: build de falhas de shard nº 52975: build de falhas de shard nº 52988: build de falhas de shard nº 52996: build de falhas de shard nº 52998: build de falhas de shard nº 53007: build de falhas de shard #53013: build de falhas de shard #53014: build de falhas de shard #53015: build de falhas de shard #53026: build de falhas de shard #53027: build de falhas de shard #53032: build de falhas de shard #53033: build de falhas de shard #53035: build de falhas de shard #53041: shard failedbuild #53047: shard failedbuild #53056: shard failedbuild #53059: nenhuma falha (execução parcial)build #53077: shard failedbuild #53083: nenhuma falha (execução parcial)build #53086: shard failedbuild #53090: shard failedbuild #53095: shard failedbuild #53106: build de falhas de shard #53109: build de falhas de shard #53123: build de falhas de shard #53127: build de falhas de shard #53130: build de falhas de shard #53131: build de falhas de shard #53133: build de falhas de shard #53134: build de falhas de shard #53135: build de falhas de shard #53143: shard failedbuild #53149: shard failedbuild #53159: shard failedbuild #53164: shard failedbuild #53167: todos os shards PassBuild #53172: todos os shards PassBuild #53176: todos os shards PassBuild #53188: shard FailuresBuild #53194: todos shards PassBuild #53208: todos os shards PassBuild #53212: shard FailuresBuild #53213: shard FailuresBuild #53214: shard FailuresBuild #53216: todos os shards PassBuild #53222: todos os shards PassBuild #53229: todos os shards PassBuild #53236: sem falhas (execução parcial) build #53241: todos os shards PassBuild #53260: todos os shards PassBuild #53265: todos os shards PassBuild #53271: todos os shards PassBuild #53280: nenhuma falha (execução parcial)Build #53304: shard FailuresBuild #53327: todos os shards PassBuild #53340: todos os shards PassBuild #53360: sem falhas (execução parcial)build #53419: sem falhas (execução parcial)build #53431: shard failedbuild #53458: sem falhas (execução parcial)build #53485: shard failedbuild #53491: todos os shards passarambuild #53503: todos os shards passarambuild #53514: sem falhas (execução parcial)build #53570: shard failedbuild #53583: shard failedbuild #53599: shard failedbuild #53748: shard failedbuild #53753: todos os shards passarambuild #53759: sem falhas (execução parcial)build #53781: shard failedbuild #53787: sem falhas (execução parcial)build #53811: todos os shards PassBuild #53863: shard FailuresBuild #53893: shard FailuresBuild #53914: todos os shards PassBuild #53933: shard FailuresBuild #53952: todos os shards PassBuild #53983: shard FailuresBuild #53992: shard FailuresBuild #53999: shard FailuresBuild #54008: sem falhas (execução parcial)build #54012: todos os shards passarambuild #54015: todos os shards passarambuild #54017: todos os shards passarambuild #54022: shard failedbuild #54026: todos os shards passarambuild #54030: shard failedbuild #54033: todos os shards passarambuild #54040: sem falhas (execução parcial) build #54047: shard failedbuild #54049: shard failedbuild #54055: shard failedbuild #54057: shard failedbuild #54064: todos os shards passaram na compilação #54074: todos os shards passaram na compilação #54083: sem falhas (execução parcial)build #54093: todos os shards foram aprovados na compilação #54144: todos os shards foram aprovados na compilação #54161: shard failedbuild #54186: shard failedbuild #54189: shard failedbuild #54196: shard failedbuild #54202: todos os shards foram aprovados

macOS arm64 · 4 fragmentos

compilação nº 52897: build de falhas de shard nº 52929: build de falhas de shard nº 52932: build de falhas de shard nº 52944: build de falhas de shard nº 52975: build de falhas de shard nº 52996: build de falhas de shard nº 52998: build de falhas de shard nº 53007: build de falhas de shard nº 53013: build de falhas de shard #53014: build de falhas de shard #53015: build de falhas de shard #53026: build de falhas de shard #53027: build de falhas de shard #53032: build de falhas de shard #53035: build de falhas de shard #53041: build de falhas de shard #53047: build de falhas de shard #53056: build de falhas de shard #53059: build de falhas de shard #53077: build de falhas de shard #53095: build de falhas de shard #53109: build de falhas de shard #53123: build de falhas de shard #53127: build de falhas de shard #53130: build de falhas de shard #53131: build de falhas de shard #53133: build de falhas de shard #53134: build de falhas de shard #53135: build de falhas de shard #53143: build de falhas de shard #53149: build de falhas de shard #53159: build de falhas de shard #53164: build de falhas de shard #53167: build de falhas de shard #53172: build de falhas de shard #53176: shard failedbuild #53188: shard failedbuild #53194: shard failedbuild #53208: shard failedbuild #53212: shard failedbuild #53213: shard failedbuild #53214: shard failedbuild #53216: shard failedbuild #53222: shard failedbuild #53229: não falhas (execução parcial) build # 53236: sem falhas (execução parcial) build # 53241: todos os shards passaram build # 53265: todos os shards passaram build # 53271: sem falhas (execução parcial) build # 53280: sem falhas (execução parcial) build # 53304: falhas de shard build # 53327: sem falhas (execução parcial) build #53340: sem falhas (execução parcial) build #53360: sem falhas (execução parcial) build #53368: shard failedbuild #53379: shard failedbuild #53383: shard failedbuild #53401: shard failedbuild #53431: shard failedbuild #53458: shard failedbuild #53491: não falhas (execução parcial) build #53503: shard failedbuild #53570: shard failedbuild #53583: shard failedbuild #53599: shard failedbuild #53601: shard failedbuild #53748: shard failedbuild #53753: todos os shards passarambuild #53757: sem falhas (execução parcial)build #53759: sem falhas (execução parcial)build #53787: sem falhas (execução parcial)build #53811: todos os shards aprovadosbuild #53952: sem falhas (execução parcial)build #53992: shard failedbuild #53999: shard failedbuild #54007: sem falhas (execução parcial)build #54012: todos os shards passarambuild #54015: shard failedbuild #54017: todos os shards passaram na build #54022: shard failedbuild #54026: nenhuma falha (execução parcial)build #54030: shard failedbuild #54033: todos os shards passaram na build #54040: shard failedbuild #54047: shard failedbuild #54049: build de falhas de shard #54055: build de falhas de shard #54057: build de falhas de shard #54064: build de falhas de shard #54074: build de falhas de shard #54093: build de falhas de shard #54161: build de falhas de shard #54186: build de falhas de shard #54189: build de falhas de shard #54196: shard failedbuild #54202: todos os shards foram aprovados

Windows x64 · 8 fragmentos

compilação nº 53090: build de falhas de shard nº 53094: build de falhas de shard nº 53095: build de falhas de shard nº 53106: build de falhas de shard nº 53109: build de falhas de shard nº 53123: build de falhas de shard nº 53127: build de falhas de shard nº 53130: build de falhas de shard nº 53131: build de falhas de shard #53133: build de falhas de shard #53134: build de falhas de shard #53135: build de falhas de shard #53143: build de falhas de shard #53149: build de falhas de shard #53159: build de falhas de shard #53164: build de falhas de shard #53167: build de falhas de shard #53172: build de falhas de shard #53176: build de falhas de shard #53188: build de falhas de shard #53194: build de falhas de shard #53208: build de falhas de shard #53212: build de falhas de shard #53213: build de falhas de shard #53214: build de falhas de shard #53216: build de falhas de shard #53222: build de falhas de shard #53229: build de falhas de shard #53236: build de falhas de shard #53241: build de falhas de shard #53260: build de falhas de shard #53265: build de falhas de shard #53271: build de falhas de shard #53280: build de falhas de shard #53298: build de falhas de shard #53304: shard failedbuild #53327: todos os shards passarambuild #53340: todos os shards passarambuild #53360: sem falhas (execução parcial)build #53419: shard failedbuild #53431: shard failedbuild #53458: shard failedbuild #53470: sem falhas (execução parcial)build #53485: shard failedbuild #53491: sem falhas (execução parcial) build #53503: shard failedbuild #53514: sem falhas (execução parcial)build #53565: sem falhas (execução parcial)build #53570: shard failedbuild #53599: shard failedbuild #53745: shard failedbuild #53748: shard failedbuild #53753: build de falhas de shard #53757: build de falhas de shard #53759: build de falhas de shard #53762: build de falhas de shard #53769: build de falhas de shard #53781: build de falhas de shard #53787: build de falhas de shard #53808: build de falhas de shard #53811: build de falhas de shard #53852: shard failedbuild #53863: shard failedbuild #53883: shard failedbuild #53893: shard failedbuild #53914: todos os shards passarambuild #53933: todos os shards passarambuild #53952: todos os shards passarambuild #53973: sem falhas (execução parcial)build #53983: shard failedbuild #53992: shard failedbuild #53999: shard failedbuild #54002: sem falhas (execução parcial)build #54004: sem falhas (execução parcial)build #54007: shard failedbuild #54008: sem falhas (execução parcial)build #54012: todos os shards passarambuild #54015: todos shards PassBuild #54017: todos os shards PassBuild #54022: shard FailuresBuild #54026: todos os shards PassBuild #54030: todos os shards PassBuild #54033: todos os shards PassBuild #54040: todos os shards PassBuild #54047: shard FailuresBuild #54049: shard FailuresBuild #54055: shard failedbuild #54057: shard failedbuild #54064: todos os shards PassBuild #54074: todos os shards PassBuild #54083: todos os shards PassBuild #54093: todos os shards PassBuild #54144: shard FailuresBuild #54161: shard FailuresBuild #54186: shard failedbuild #54189: shard failedbuild #54196: shard failedbuild #54202: todos os shards foram aprovados

Windows arm64 · 8 fragmentos

compilação nº 53090: build de falhas de shard nº 53095: build de falhas de shard nº 53106: build de falhas de shard nº 53109: build de falhas de shard nº 53123: build de falhas de shard nº 53127: build de falhas de shard nº 53130: build de falhas de shard nº 53131: build de falhas de shard nº 53134: shard failedbuild #53135: shard failedbuild #53149: shard failedbuild #53159: shard failedbuild #53164: shard failedbuild #53167: shard failedbuild #53172: shard failedbuild #53176: shard failedbuild #53188: shard failedbuild #53194: shard failedbuild #53208: shard failedbuild #53212: shard failedbuild #53213: shard failedbuild #53214: shard failedbuild #53216: shard failedbuild #53222: shard failedbuild #53229: shard failedbuild #53236: sem falhas (execução parcial)build #53241: shard failedbuild #53260: shard failedbuild #53265: shard failedbuild #53271: shard failedbuild #53304: shard failedbuild #53327: todos os shards passarambuild #53340: todos os shards passarambuild #53360: sem falhas (execução parcial)build #53419: não falhas (execução parcial) build #53431: shard failedbuild #53458: sem falhas (execução parcial)build #53485: shard failedbuild #53491: sem falhas (execução parcial)build #53503: sem falhas (execução parcial)build #53599: shard failedbuild #53748: shard failedbuild #53753: shard fracassosbuild #53757: falhas de shardbuild #53759: falhas de shardbuild #53762: falhas de shardbuild #53787: falhas de shardbuild #53808: falhas de shardbuild #53811: falhas de shardbuild #53852: falhas de shardbuild #53863: falhas de shardbuild #53883: shard failedbuild #53893: shard failedbuild #53914: sem falhas (execução parcial)build #53933: todos os shards PassBuild #53952: todos os shards PassBuild #53983: shard FailuresBuild #53992: shard FailuresBuild #53999: shard FailuresBuild #54007: sem falhas (execução parcial)build #54012: todos os shards PassBuild #54015: todos os shards PassBuild #54017: todos os shards PassBuild #54022: shard FailuresBuild #54026: todos os shards PassBuild #54030: sem falhas (execução parcial)Build #54033: todos os shards PassBuild #54040: todos os shards PassBuild #54047: shard failedbuild #54049: shard failedbuild #54055: sem falhas (execução parcial)build #54057: shard failedbuild #54064: todos os shards passarambuild #54074: todos os shards passarambuild #54083: sem falhas (execução parcial)build #54093: todos os shards passarambuild Nº 54144: build de falhas de shard #54161: build de falhas de shard #54186: build de falhas de shard #54189: build de falhas de shard #54196: build de falhas de shard #54202: todos os shards foram aprovados

Shards de teste de cada build de CI, por plataforma, em 135 builds que executaram testes (420 extraídos do BuildKite). Verde brilhante: cada fragmento passou. Verde escuro: sem falhas, mas a corrida foi interrompida (substituída). Vermelho: pelo menos um fragmento falhou. Cada pista é carimbada quando seu conjunto completo é aprovado pela primeira vez – os 60 fragmentos do Linux estavam verdes quase um dia inteiro antes do Windows. As plataformas continuaram oscilando em vermelho até que os últimos testes reprovados caíram; a versão final totalmente ecológica foi #54202.

O resto do tempo que antecedeu a fusão foi simples. Um fluxo de trabalho que corrigia falhas de teste de CI para cada plataforma até que não houvesse mais falhas de teste. Vários fluxos de trabalho para limpeza relacionada ao Windows, para desduplicar código, para reduzir o uso inseguro e para limpar alguns códigos em geral.

Mesclando a reescrita Rust

Depois que 100% do conjunto de testes do Bun passou no CI em todas as plataformas (e eu verifiquei manualmente que os testes estavam de fato sendo executados e não sendo ignorados), executei vários comandos localmente para testar as coisas - e então pressionei o botão de mesclagem.

Mesclar em main não é uma versão versionada. Neste ponto, eu estava confiante o suficiente para seguir em frente e me comprometer com a reescrita, mas ainda não confiante o suficiente para lançá-la.

Estatísticas

No pico, estávamos executando quatro desses fluxos de trabalho ao mesmo tempo, cada um em uma árvore de trabalho separada, cada um com 16 Claudes por fluxo de trabalho. Cerca de 64 Claudes por vez.

git log · claude/phase-a-portpeak: 58 commits em um minuto

52

confirmações

+796,601

linhas escritas, reescritas incluídas

Terça-feira, 5 de maio, 12h39 PDT

primeiro rascunho de 100 arquivos em lotePR #30412 openmerged

· fase-a: rascunho do lote head-1 (100 arquivos)+12.379−0

· fase-b1: caminhos + compilação de string (rascunhos de portas; codificação mínima/strings/superfície lexer)+2.312−2.149

Todos os 6.502 commits (mesclagem excluída), reproduzidos. As barras rosa são, em sua maioria, códigos novos; barras ciano são principalmente apagadas. O contador de linha conta cada reescrita ao longo do caminho – a diferença obtida foi +1.009.272. O log contém mensagens de commit reais.

0 testes ignorados ou excluídos

11 dias (3 de maio → fusão em 14 de maio) · 6.778 commits

Plataformachamadas expect()TestesArquivos
Debian 13x641,386,82660,6244,174
macOS 14 arm641,259,95358,8504,175
Windows 2019 x641,007,54457,3374,173

Pré-fusão, foram necessários 5,9 bilhões de tokens de entrada não armazenados em cache, 690 milhões de tokens de saída e 72 bilhões de leituras de tokens de entrada armazenados em cache – cerca de US$ 165.000 pelo preço da API. Manualmente, acho que isso levaria cerca de um ano para três engenheiros com contexto completo na base de código, período durante o qual não seríamos capazes de melhorar a compatibilidade do Node.js, corrigir bugs, corrigir problemas de segurança ou implementar novos recursos. Nós nunca teríamos feito isso. A alternativa realista era não fazer nada e continuar corrigindo os bugs no topo deste post para sempre.

Esta é a vanguarda do que é possível hoje. Usei uma versão de pré-lançamento de Claude Fable 5, um modelo da classe Mythos. Os fluxos de trabalho dinâmicos de Claude Code mantiveram 64 Claudes funcionando por 11 dias (caso contrário, eu teria que escrever meu próprio equipamento para conseguir isso).

O trabalho continua

Desde a fusão da porta Rust, concluímos 11 rodadas de análise de segurança da Claude Code Segurança e abordamos as descobertas.

Também adicionamos fuzzing guiado por cobertura 24 horas por dia, 7 dias por semana, de cada analisador em Bun — JavaScript, TypeScript, JSX, CSS, JSON5, JSONC, TOML, YAML, Markdown, INI, Bun Shell scripts, intervalos semver, arquivos .patch e cores CSS. O fuzzer envia automaticamente os bugs encontrados para Claude enviar um PR de reprodução e correção, e os humanos revisam os PRs. Até agora, nossos analisadores foram executados 100 bilhões de vezes, o que resultou em cerca de 15 PRs.

No momento em que este artigo foi escrito, cerca de 4% do código Rust de Bun estava dentro de um bloco unsafe (~13.000 palavras-chave unsafe em ~27.000 linhas / ~780.000 linhas), e 78% desses blocos são uma única linha - um ponteiro que veio de C++ ou uma chamada para uma biblioteca C. Espero que esse número diminua com o tempo, à medida que refatoramos de uma porta Zig fiel (que não tinha nenhuma palavra-chave unsafe greppable) para Rust idiomática, mas continuaremos usando bibliotecas C e C++ como JavaScriptCore para que sempre tenha mais unsafe do que projetos Rust puros.

Erros de portabilidade

O foco da reescrita de Rust é a estabilidade, mas seria impossível realizar uma mudança massiva como essa e introduzir regressões zero.

Essa reescrita introduziu 19 regressões conhecidas, cada uma delas corrigida.

A maioria das regressões veio de códigos que são sintaticamente idênticos nas duas linguagens, mas semanticamente diferentes.

Efeito colateral dentro de debug_assert!

Esses dois trechos parecem semelhantes, mas se comportam de maneira diferente. O assert de Zig é uma função, portanto seu argumento é executado em cada compilação. O debug_assert! de Rust é uma macro; por isso, nas compilações de produção, toda a expressão é eliminada, inclusive a chamada 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 adiciona um arquivo ao gráfico de recarga dinâmica do servidor de desenvolvimento frontend. Nas compilações de lançamento, ele parou de funcionar e o HMR quebrou em certos casos para projetos com rotas HTML que usam React enquanto um arquivo recarregado a quente é invalidado: Cannot destructure property 'isLikelyComponentType' of 'k'. As compilações de depuração funcionaram. #30678

Fatias de comprimento ímpar

Bun (anterior a conversões integradas que suportam fatias) usou @divTrunc e ignorou um byte ímpar final. bytemuck::cast_slice entra em pânico com isso. Blob.text() em uma marca de ordem de bytes UTF-16 seguida por um número ímpar de bytes parou de retornar uma string e colocou o processo em pânico. Voltamos a ignorar o byte estranho: &buf[..buf.len() & !1]. #31188

Verificações de limites

No macOS e Linux, compilamos o código Zig de Bun com ReleaseFast, que remove verificações de limites. As compilações de lançamento de Rust os mantêm.

Bun armazena nomes de arquivos longos em uma lista global que se espalha em blocos de overflow. O código Zig original dimensionava cada bloco em count / 4 ou 2048. A porta deixou um espaço reservado:

/// ... so use a nonzero stand-in until Phase B threads the
/// per-instantiation value through.
pub const BSS_OVERFLOW_BLOCK_SIZE: usize = 64;

Isso reduziu o teto de 8,4 milhões de nomes de arquivos internos para 270.272, o que os projetos reais atingiram, e tornou acessível um ptrs[4095] separado por um que portamos de Zig. Rust entrou em pânico em vez de escrever além do fim. Zig também entraria em pânico neste caso, se usássemos ReleaseSafe (só fizemos no Windows). #31503

comptime formatar strings

Output.pretty reescreve marcadores de cores <r> e <d> em escapes ANSI. Em Zig, fmt é comptime, então os marcadores desaparecem antes que os argumentos sejam substituídos. As funções Rust não têm parâmetros de tempo de compilação, então Output::pretty só viu a string finalizada e reescreveu os marcadores sobre os argumentos também.

// 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 nomes de pacotes como hiperlinks OSC 8, terminados por ESC \. Essa barra invertida fica logo antes do < do <r> final, o analisador do marcador a come e o r é impresso como texto.

Em Rust tem que ser uma macro: bun_core::pretty!("<r>{}<r>", hyperlink). #30693

Bun é melhor em Rust

Até agora, Bun v1.4.0 corrige 128 bugs que se reproduzem na v1.3.14. Eles variam de vazamentos de memória a falhas e textos de ajuda com cores incorretas.

Uso de memória reduzido

Rust possui uma ferramenta poderosa em nível de linguagem para limpar memória: Drop. Quando Drop é implementado, a função drop é chamada automaticamente sempre que o valor sai do escopo.

impl Drop for Bytes {
    fn drop(&mut self) {
        if !self.pinned.is_empty() {
            JSC__JSValue__unpinArrayBuffer(self.pinned);
        }
    }
}

Em Zig, defer pode ser usado para executar código no final de um escopo:

const bytes: ArrayBuffer = try .fromPinned(global, value);
defer bytes.unpin();

Em Zig, defer precisa ser adicionado a cada site de chamada individual que possa precisar de limpeza. É fácil acabar esquecendo de limpar (um vazamento de memória) ou de executar o código de limpeza duas vezes em um código de tratamento de erros raramente alcançado (uma liberação dupla). Em Rust, Drop é executado automaticamente quando o valor não está mais acessível - trocando "nenhum fluxo de controle oculto" para evitar uma arma de fogo comum.

Drop corrigiu vários vazamentos de memória em Bun relacionados a caminhos de arquivo no código de tratamento de erros.

Corrigimos todos os vazamentos de memória instrumentável

Melhoramos a integração do LeakSanitizer do Bun para rastrear todas as alocações de memória de código nativo.

Aqui está um exemplo: cada chamada Bun.build() em processo vazou vários megabytes de memória — texto fonte analisado e tabelas de símbolos AST que sobreviveram à compilação à qual pertenciam.

// 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",
  });
}

No Bun v1.3.14, cada compilação vaza cerca de 3 MB, para sempre — ferramentas como servidores de desenvolvimento agrupados em cada solicitação eventualmente ficam sem memória. Em Bun v1.4.0, níveis de memória desativados:

ConstruçõesBun v1.3.14Bun v1.4.0
5001.914MB526MB
1,0003.506 MB586MB
1,5005.097 MB608MB
2,0006.745 MB609MB

Uma tentativa anterior de fazer isso em Zig não foi mesclada porque a falta de um equivalente de Drop tornou mais difícil sentir-se confiante na fusão.

Tamanho binário menor

As alterações iniciais na reescrita Rust reduziram o tamanho binário em 3,8 MB no Windows, 5,5 MB no macOS e 6,8 MB no Linux. Isso ocorre principalmente porque usamos muito comptime em nosso código Zig.

Após essa redução inicial, a equipe explorou mais oportunidades para redução do tamanho binário usando otimizações de linker, como dobramento de código idêntico, remoção de dados não utilizados do ICU e descompactação preguiçosa de pequenas partes do libicu com um dicionário zstd sob demanda.

Combinado com a reescrita de Rust, alterações na ICU e dobramento de código idêntico, o tamanho binário de Bun diminui em ~20% no Linux e no Windows.

VersãoPlataformaTamanho
Bun v1.4.0 (canário)Windows76MB
Bun v1.3.14Windows94MB
Bun v1.4.0 (canário)Linux70MB
Bun v1.3.14Linux88MB

Uso reduzido de espaço de pilha

O analisador TOML e todos os outros analisadores descendentes recursivos em Bun (JSON, YAML, JavaScript, TypeScript e mais) agora usam menos espaço de pilha.

Isso causou algumas falhas nos testes antes de mesclar a reescrita 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 emite intrínsecos llvm.lifetime.start e llvm.lifetime.end do LLVM para variáveis de pilha quando elas não estão mais em uso, o que permite que o LLVM reutilize slots de espaço de pilha. Isso permite que funções grandes com escopos aninhados usem significativamente menos espaço de pilha.

Anteriormente, solucionamos manualmente um problema em aberto refatorando funções particularmente grandes em muitas funções menores.

2% - 5% mais rápido

Rust suporta otimização de tempo de link entre linguagens entre C/C++ e Rust, o que permite inlining entre linguagens de programação (que legal é isso!!).

Comparamos o Bun v1.3.14 com o Bun v1.4.0 no Linux x64 (EC2, Xeon Platinum 8488C). Taxa de transferência HTTP medida com oha em relação a servidores hello-world, cargas de trabalho de aplicativos medidas com hyperfine.

Taxa de transferência HTTP (req/s, média de 3 rodadas)

servidorBun v1.3.14Bun v1.4.0Δ
Bun.serve169.6k177.7k+4.8%
node:http103.8k108.5k+4.5%
Elysia158.9k163.3k+2.8%
express64.5k66.6k+3.2%
fastify91.5k95.9k+4.8%

Aplicativos/CLI (hyperfine)

carga de trabalhoBun v1.3.14Bun v1.4.0Δ
next build13.62 s13.03 s+4.5%
vite build (tsc + vite)1.69 s1.65 s+2.2%
tsc -b --force0.94 s0.89 s+4.7%

Produção

Prisma lançou a versão beta pública do Prisma Compute na reescrita de Bun Rust.

"Encontramos vazamentos de memória e um pool de conexões que não pôde ser recuperado depois que uma VM foi pausada e retomada. Quando a reescrita Rust apareceu, nós a testamos nos mesmos modos de falha. Ela os tratou perfeitamente." - Alexei Orlenko

Claude Code v2.1.181 (lançado em 17 de junho) e posteriormente usa a porta Rust de Bun. A inicialização ficou 10% mais rápida no Linux, mas por outro lado, quase ninguém percebeu. Chato é bom.

Envio

Bun v1.3.14 foi a última versão de Bun escrita em Zig. Bun v1.4.0 será a primeira versão de Bun escrita em Rust. Ele já está disponível em canário. Informe qualquer problema que encontrar:

bun upgrade --canary

Manutenção

Para mim e para a equipe, nossa nova base de código Rust é muito semelhante à antiga base de código Zig. Por exemplo, aqui está um trecho do código Zig original e do novo código 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;
        }

        // ...
    }

    // ...
}

Qualquer pessoa que entende o código Zig original entende o código Rust traduzido mecanicamente. Eu revisei o PR de reescrita Rust original, verificando se os agentes de revisão de código adversários estavam detectando corretamente as discrepâncias entre o código Zig e o código Rust, se eles estavam garantindo que o guia de portabilidade e o guia vitalício estavam sendo seguidos, e também lendo manualmente grande parte do código lado a lado com o Zig vs Rust.

O que vem a seguir

Bun v1.4 torna o Bun mais rápido, menor, usa menos memória e fornece à equipe ferramentas incrivelmente poderosas para melhorar sistematicamente a estabilidade daqui para frente: o verificador de empréstimo do Rust, Miri (que executa uma parte crescente de código em CI), LeakSanitizer e fuzzing guiado por cobertura 24 horas por dia, 7 dias por semana para analisadores. Ainda há mais para refatorar, mas as coisas começaram muito bem.

Essa reescrita do Rust levaria um ano de trabalho para uma equipe de engenheiros com contexto completo na base de código. Com 1 engenheiro usando Fable e monitorando de perto Claude Code, passamos do início para 100% de aprovação do conjunto de testes em todas as plataformas em 11 dias.

Um engenheiro pode fazer muito mais hoje do que há um ano.

Os direitos de autor pertencem ao autor e aos respetivos titulares. Para correções ou pedidos de remoção, contacte a CanWin AI.