Cómo Bun reescribió 500,000 líneas de Zig a Rust usando Claude Code
Hace unos meses, el creador de Bun —el runtime de JavaScript alternativo a Node.js— hizo una reescritura completa de su proyecto: pasó del lenguaje Zig al lenguaje Rust. Y aunque esto ocurrió hace ya varios meses y muchos están usando esa reescritura sin siquiera saberlo, finalmente tenemos un artículo oficial donde Jarred Sumner, el líder del proyecto, explica cómo lo hizo posible.
Es un documento bastante técnico, pero responde una pregunta que muchos se hicieron: ¿cómo configuraron todo para migrar medio millón de líneas de código y terminar con más de un millón de líneas en Rust? Un dato clave adicional: el autor tuvo acceso anticipado a Claude Fable 5 antes de su lanzamiento, y eso —sumado a su conocimiento técnico y a las configuraciones que hizo en Claude Code— fue lo que permitió llevar a cabo la reescritura de un proyecto que hoy supera los 22 millones de descargas mensuales.
¿Qué es Bun y por qué importa tanto?
Para quienes no lo conocen, Bun es un proyecto que permite ejecutar código JavaScript en el sistema operativo, muy similar a Node.js o Deno. Sirve para crear herramientas de consola o programas mucho más avanzados con JavaScript. De hecho, proyectos como Claude Code, OpenCode o Tailwind CSS lo utilizan por debajo. Y Claude Code es el que más depende de él, porque —si recuerdan— Anthropic adquirió Bun a finales del año pasado.
Bun no es un proyecto pequeño: tiene alrededor de 500,000 líneas de código. En años anteriores, una reescritura de este tamaño habría requerido a todo un equipo de desarrollo trabajando durante un año completo. Pero como Bun ahora es parte de Anthropic, Jarred Sumner tuvo acceso a una versión anticipada del modelo al momento de hacer la migración.
A diferencia de Node, que es solamente un entorno de ejecución, Bun es como si fuera Node más un linter, más herramientas de testing, más bibliotecas para conexión a bases de datos e incluso implementaciones de APIs del frontend. Un proyecto mucho más ambicioso desde el inicio. Y quienes le prestaron atención en los últimos años pudieron ver cómo avanzaba a un ritmo impresionante: el perfil de GitHub de Jarred muestra una actividad enorme desde antes de la era de las IAs, con una cantidad de commits diarios que hoy parecería asistida por agentes, pero que en su momento era puro trabajo manual.
Esa velocidad llevó a Bun a ser adoptado por proyectos importantes y a alcanzar los 22 millones de descargas mensuales, con soporte de las nubes más grandes del momento: Vercel, Railway, DigitalOcean y varias más.
El problema: la memoria en Zig
En el artículo se mencionan los tipos de errores que encontraban constantemente en el código: fallos de manejo de memoria, manejo de errores, conexiones TCP y UDP. Se repetían tanto que prácticamente el trabajo continuo del equipo era reparar ese tipo de bugs.
Intentaron mitigarlo con varias herramientas:
- ASan (AddressSanitizer), el proyecto de Google que verifica fallos de memoria.
- Fuzzing, para inyectar enormes cantidades de entradas no sanitizadas y detectar errores.
- Tests de memory leaks y otras verificaciones.
Aun así, seguían apareciendo bugs de estabilidad en Bun, y el equipo ya estaba bastante cansado de la situación.
El problema de fondo es que tanto Zig como C no manejan la memoria de forma automática. Es normal en lenguajes de bajo nivel: la liberación de memoria se hace de manera explícita (en Zig, con la palabra clave defer). El equipo intentaba que estos fallos se detectaran al momento de escribir el código —y no cuando ya pasaban al merge o al pipeline de CI/CD— apoyándose en guías de estilo como Tiger Style o el C++ Style Guide. Pero al final, todo dependía demasiado de la supervisión humana, y los fallos de memoria seguían apareciendo.
¿Por qué no C++?
Una pregunta obvia: ¿por qué no usar C++, que ofrece constructores y destructores automáticos y les permitiría eliminar buena parte del código repetitivo? La respuesta del artículo es que igual habrían estado forzados a seguir una guía de estilo, y C++ tampoco da garantías contra la corrupción de memoria, que es algo muy común. Esa fue una de las razones por las que eligieron Rust.
¿Por qué Rust?
Rust es conocido por tener un compilador enfocado precisamente en evitar fallos de memoria en tiempo de compilación. Aun así, lo pensaron varias veces: Bun es un proyecto enorme y las reescrituras, por lo general, no son buena idea. Hacerla con un equipo de desarrolladores habría tomado alrededor de un año, y esperar tanto no era viable. También consideraron implementar algo similar a los punteros de Rust desde cero, pero eso habría terminado con peor ergonomía que la de Rust y sin ninguna de las garantías de su compilador.
Y es ahí donde entra Claude.
La estrategia: no fue "reescribe Bun en Rust"
Lo interesante es que la reescritura no fue delegada completamente a la IA. No escribieron un prompt del estilo "reescribe Bun en Rust y no cometas errores". La estrategia fue mucho más pensada.
¿Incremental o todo a la vez?
Muchos se preguntarían si convenía una reescritura incremental o de todo al mismo tiempo. Jarred ya tenía experiencia haciendo ports —hizo el port de esbuild (escrito en Go) en las primeras versiones de Bun— y su conclusión es que es mucho mejor hacerlo todo al mismo tiempo. Una reescritura incremental te obliga a escribir código temporal que eventualmente tienes que eliminar, y cada modificación intermedia consume mucho más tiempo.
Transpilar primero, refactorizar después
El enfoque fue reescribir el código de tal forma que pareciera simplemente código transpilado de Zig a Rust. Como ese código transpilado iba a tener una enorme cantidad de bloques unsafe, la idea era refactorizar progresivamente para hacerlo más idiomático en Rust: una revisión encima del código transpilado.
El trabajo previo: porting.md
Antes de pedirle cualquier cosa a Claude Code, Jarred pasó horas conversando con el agente para definir patrones y cómo debería lucir el código base al pasar de Zig a Rust. Esa discusión luego la exportó a un archivo llamado porting.md, que funcionó como la memoria del proyecto durante toda la migración. Esto no es vibe coding: es planificación de proyecto automatizada.
Los workflows: 50 loops durante 11 días
Al momento de las revisiones de código, se lanzaron alrededor de 50 workflows en Claude Code durante 11 días: loops que ejecutaban revisiones una y otra vez sobre gran parte del código, haciendo ports de archivos, corrigiendo errores de crates a medida que iteraban, creando subcomandos del CLI y ejecutando cada test repetidamente.
Un ejemplo de los prompts: generar un workflow que revisara cada struct del código con indicaciones específicas de qué hacer, proponiendo lifetimes cuando alguno resultaba complejo. Y el avance no era de golpe, sino archivo por archivo —de hecho, de tres en tres archivos— para poder hacer las pruebas iniciales.
También hay un detalle de ingeniería interesante en la administración de los cambios: como es un loop, crear un git worktree (una copia del proyecto) por cada modificación terminaría llenando el disco. Lo que hace es que, al terminar una iteración, ejecuta el commit, libera y continúa con la siguiente. Básicamente, liberar espacio en cada iteración.
Revisión adversarial: un agente implementa, dos lo atacan
Aquí viene la parte más interesante: ¿cómo confías en más de un millón de líneas generadas por un LLM? La respuesta del autor es que se debe mejorar el proceso de generación del código en lugar de estar reparándolo a mano. Y para eso implementó algo llamado revisión adversarial.
La idea es simple de entender: cuando se hace code review entre humanos, quien escribe el código es una persona y quien revisa es otra. El que escribe quiere que se apruebe; el que revisa busca errores. Con los LLMs pasa exactamente igual, así que el sistema replica esa dinámica:
- Un agente implementa. No revisa nada, solamente escribe código.
- Dos agentes hacen de reviewers. Su único trabajo es buscar bugs y razones por las que el código no funciona. Van línea por línea tratando de encontrarle todos los fallos posibles al primer agente.
Como el código se genera en loop, este tipo de revisión continua es indispensable en el momento mismo de la generación.
Los números de la migración
Corriendo todo esto en paralelo, Claude llegaba a escribir 1,300 líneas de código por minuto, mientras los dos reviewers verificaban lo que se generaba. En los días de iteración continua —24 horas al día— se llegaron a hacer 695 commits por hora.
Pero a ese punto solo existía la reescritura: nada del código funcionaba todavía. La siguiente tarea de Claude fue corregir los errores en tiempo de compilación, uno por uno.
Aquí hubo un momento curioso: en cierto punto, Claude entendió que necesitaba que todos los crates compilaran y empezó a añadir comentarios dentro de las funciones para "resolver" los errores. La solución fue añadir una regla explícita: si tienes que añadir un comentario largo, entonces el código está mal; repara el código. Gracias a eso, el proyecto pasó todos los tests en local, luego el CI, y finalmente la reescritura en Rust quedó completa, superando el millón de líneas de código.
El costo: $15,000 en tokens
El gasto total, al precio de la API de Fable 5, fue de alrededor de $15,000 en tokens para hacer todo el port. Puede sonar a mucho, hasta que lo comparas con la alternativa: tres ingenieros contratados durante un año. Y todo esto se hizo en cuestión de días.
No todo está terminado: el 4% unsafe
Algo muy importante: esto no significa que el código ya esté perfecto. Gran parte del código reescrito aún se mantiene como unsafe —un comentario bastante común en redes sociales cuando se publicó—: el 4% del código en Rust está marcado como unsafe por el momento. Esto es consecuencia directa de la estrategia de transpilación, y el equipo trabaja todos los días en continuar la refactorización y el reviewing.
¿La migración trajo errores? También. Se han detectado nuevos errores de regresión producto de que la IA trató de transpilar código de Zig a Rust, que obviamente no son lenguajes que se parezcan mucho.
Los resultados
Con todo, la conclusión del equipo es que Bun es mucho mejor ahora reescrito en Rust. Un ejemplo concreto: en Rust pueden llamar a la función de limpieza de memoria cada vez que quieran —incluso de forma automática—, mientras que en Zig esa función solo se podía llamar al final de un scope y de forma manual, con el riesgo de olvidarla e introducir más bugs. Eso les ha permitido reparar muchos de los fallos de memoria históricos de Bun.
Otros resultados medibles:
- Binario más liviano: el instalador de Bun redujo su peso en Windows, Linux y Mac respecto a versiones anteriores.
- Parsers más eficientes: los parsers internos para formatos como JSON, YAML o TOML ahora usan menos espacio.
- Mejor rendimiento: aplicaciones construidas sobre Bun —como Prisma o Claude Code— reportaron mejoras de alrededor del 10% en entornos como Linux.
Y quizás lo más notable: las aplicaciones que corren sobre Bun no notaron ninguna diferencia. La reescritura fue completa y transparente.
Conclusión
Este artículo es valioso no solo por lo técnico, sino porque responde las preguntas que todos nos hacemos cuando vemos este tipo de migraciones: ¿qué prompts le dieron a Claude? ¿Qué configuración tenían? ¿Cómo evaluaban el resultado?
La fórmula que hizo esto posible fue la combinación de tres cosas: workflows (los loops iterando 24/7), un modelo potente como Fable 5, y planificación previa —horas de conversación con Claude solo para definir cómo hacer la migración antes de escribir una sola línea. No es magia ni vibe coding: es ingeniería de proceso aplicada a agentes.
Para quienes ya están considerando migraciones de este tipo en sus propios proyectos, este caso es probablemente la referencia más detallada que existe hasta ahora de cómo configurar una reescritura masiva asistida por IA.