1 puntos por GN⁺ 3 시간 전 | 1 comentarios | Compartir por WhatsApp
  • Hace unos 3 años, cuando escribí directamente una VM de bytecode y un recolector de basura en Zig y unsafe Rust, la ergonomía amigable para humanos de Zig llevaba ventaja, pero al entrar en la era de los agentes de programación, esa ventaja se volvió prácticamente irrelevante
  • La mejora de productividad para desarrolladores de 1.5x a 5x que ofrecen las funciones principales de Zig queda opacada por la productividad 100x que brindan los agentes de programación basados en Rust
  • La interfaz de allocators de Zig, los enteros de ancho de bits arbitrario, packed struct, comptime y otras funciones clave brillan sobre todo cuando una persona escribe el código directamente
  • El sistema de tipos de Rust es más eficaz para evitar en tiempo de compilación errores del agente mediante bounded polymorphism y la imposición de invariantes
  • En una situación donde la cantidad de código generado por agentes aumentó 100 veces, la garantía de seguridad de memoria de Rust se vuelve una ventaja decisiva frente a Zig

Cambios clave

  • Zig tenía una gran ventaja en la ergonomía del código unsafe, pero a medida que disminuye la proporción de código escrito directamente por personas, también se reduce el valor práctico de esa ventaja
  • La mejora de productividad de 1.5x a 5x para desarrolladores humanos que aportan las funciones de Zig queda eclipsada por la mejora de 100x al usar agentes de programación en Rust
  • Muchas de las funciones representativas de Zig mejoran la comodidad al escribir código manualmente, pero esa diferencia no importa tanto para los agentes de programación
  • Hace unos 3 años, al escribir una VM de bytecode y un recolector de basura con Zig y unsafe Rust, sentí que la experiencia de escribir código unsafe era mejor del lado de Zig
  • Incluso en 2026, Zig sigue siendo un buen lenguaje, pero Rust pasó a ser un lenguaje más preferido y que además encaja mejor con los agentes de programación

La interfaz de allocator de Zig

  • La interfaz de allocator de Zig permite aplicar fácilmente allocators especializados, como arena o stack fallback, para optimizar rutas de código específicas
  • Al leer una línea de entrada del usuario, la longitud de la entrada es teóricamente ilimitada, así que se necesita un allocator de heap, pero en la práctica la mayoría de las entradas son búsquedas cortas o rutas y son mucho menores a 1 KB
  • std.heap.stackFallback(256, heap_allocator) coloca un buffer de tamaño fijo en el stack y solo pasa al heap si la entrada se desborda, lo que permite manejar el caso común sin allocation en heap
  • Antes, Rust no tenía una función equivalente a la interfaz Allocator de Zig, así que si necesitabas un Vec<T> con un allocator personalizado, había que copiar la implementación de la biblioteca estándar y modificarla
  • El código fuente de las colecciones de Bumpalo tenía la forma de un fork de las colecciones estándar conectado a un bump allocator
  • Durante un tiempo, Rust nightly tuvo el trait Allocator, y ahora parece haber llegado a un nivel suficientemente bueno
  • El Allocator de Rust está basado en traits, así que usa despacho estático, a diferencia de los allocators de Zig, que están basados en vtable
  • Rust no tiene una convención generalizada en toda la comunidad de diseñar estructuras de datos en torno a parámetros de allocator como Zig, pero esa limitación importa menos ahora que la IA puede copiar y modificar código con facilidad

Enteros de ancho de bits arbitrario y packed struct

  • Los enteros de ancho de bits arbitrario de Zig y packed struct facilitan tareas como la optimización de caché de CPU con diseño orientado a datos, tagged pointers, NaN boxing y bitflags
  • Al usar una API de Obj-C con Metal mediante la Objective-C runtime C API, id puede ser un tagged pointer y no un puntero a objeto de heap alineado
  • Si se pasa un NSNumber tagged a código que asume alineación, puede producirse UB, así que hay que verificar de forma barata si es “un puntero de heap o un inmediato tagged”
  • En un layout simplificado de tagged pointers de Objective-C, el bit inferior indica “no es un puntero de heap”, los siguientes 3 bits identifican el slot de clase y los 60 bits restantes forman el payload
  • Zig puede expresar el layout de bits directamente como tipo usando enum(u3) y packed struct, como class: TaggedClass, payload: u60
  • En Zig, con @bitCast se puede pasar entre un u64 crudo y ObjcTaggedPointer, y en is_ns_number se puede comprobar is_tagged y la clase .ns_number
  • El código equivalente en Rust usa constantes como TAG_MASK, CLASS_MASK, CLASS_SHIFT y PAYLOAD_SHIFT dentro de ObjcTaggedPointer(u64), hace operaciones OR al construir y aplica máscaras al acceder
  • En Rust, el slot de clase no es un tipo real sino una constante u64, y escribirlo a mano es menos ergonómico que en Zig
  • En Rust, conviene más usar crates como bitfield o bitflags, pero ambos dependen de proc macros y no se sienten tan bien como packed struct de Zig
  • Con agentes de programación, el problema de que este tipo de código dé flojera escribirlo a mano se reduce bastante

El cambio en el valor de comptime

  • comptime de Zig es su función más vistosa, y salvo algunos lenguajes crípticos con tipos dependientes, casi no hay lenguajes que ofrezcan una evaluación en tiempo de compilación tan buena como Zig
  • En la práctica, dejé de extrañarlo tanto, y alrededor del 95% del uso era para crear estructuras de datos genéricas de tipos parametrizados
  • Un patrón representativo es fn ArrayList(comptime T: type) type, que recibe un tipo y devuelve un tipo de struct con items: []T, capacity: usize, allocator: Allocator
  • El sistema de tipos de Rust reemplaza gran parte de los genéricos estilo comptime de Zig y además puede imponer más condiciones invariantes
  • En el 5% restante de los casos, la falta de comptime sí resulta incómoda, y la única alternativa confiable es el codegen
  • Si durante el desarrollo de un juego quieres hardcodear datos de geometría de hitbox generados por herramientas dentro de una estructura de datos, en Rust tienes que hacer que Claude escriba un script que genere un archivo Rust
  • Aun así, la evaluación en tiempo de compilación no es algo que realmente se necesite con tanta frecuencia

Ventajas del sistema de tipos de Rust

  • El sistema de tipos de Rust se considera un intercambio más valioso que comptime de Zig, especialmente porque es fuerte en el área de traits/typeclasses para bounded polymorphism
  • Intentar implementar en Zig un nivel equivalente de bounded polymorphism resulta muy difícil
  • El sistema de tipos de Rust puede imponer más condiciones invariantes (invariants), lo que ayuda a prevenir errores comunes de los agentes de programación
  • En código de juegos, se usa el crate euclid para evitar un problema frecuente en programación gráfica: la confusión entre espacios de coordenadas
  • Si creas tipos especializados para cada espacio de coordenadas, como Point<Screen> o Point<World>, mezclar por error coordenadas del mundo y de la pantalla queda bloqueado en compilación
  • Si WorldPoint, WorldVector y ScreenPoint son tipos separados, entonces se permite sumar point y vector del mismo espacio
  • Con Translation2D::<f32, WorldSpace, ScreenSpace> se puede convertir explícitamente del espacio del mundo al espacio de pantalla
  • En cambio, código como let bad: ScreenPoint = player;, que asigna directamente un WorldPoint a ScreenPoint, no está permitido

El efecto de lidiar menos con problemas de memoria

  • Si los agentes de programación hacen posible escribir 100 veces más código, también aumenta 100 veces la cantidad de problemas de memoria que hay que revisar en código Zig
  • Sin verificación formal, la superficie del espacio de búsqueda que hay que inspeccionar para encontrar bugs se vuelve mucho mayor
  • En una situación como la actual, donde la cantidad de código generado creció, Rust resulta más atractivo
  • El compromiso tradicional de Rust era que perjudicaba la productividad de los desarrolladores cuando todavía no estaban familiarizados con el borrow checker, pero con agentes de programación esa desventaja pierde mucha importancia
  • Incluso usando unsafe en Rust, puedes hacer que un agente de programación ejecute herramientas como miri para verificar si no ocurre UB ni se violan las reglas de aliasing de Rust

Conclusión

  • Zig sigue siendo un lenguaje que se extraña y además es un buen lenguaje
  • En la forma de trabajar de 2026, Rust es más preferido y además tiene mejor compatibilidad con los agentes de programación

1 comentarios

 
GN⁺ 3 시간 전
Comentarios en Lobste.rs
  • Un exlíder de mi equipo tenía la idea bastante firme de que el código copiado y pegado no siempre es algo malo
    Por el principio DRY, intuitivamente sonaba incorrecto o al menos discutible, pero era una persona muy pragmática y aplicaba esta idea sobre todo a bases de código grandes de pruebas
    Su lógica era que, en vez de forzar una interfaz compartida “inteligente”, una base de código simple pero más grande y con más duplicación podía ser más fácil de mantener
    Últimamente, usando LLM, he vuelto a esa misma idea, y ahora la estoy aplicando incluso a partes más importantes del software
    Generar código es rápido, y parece probable que los LLM también acierten más con una base de código simple pero muy duplicada

    • Especialmente en pruebas, soy completamente WET, o sea, del lado de “disfrutemos teclear”
      Si metes demasiada abstracción en los tests para reducir duplicación, se vuelven más difíciles de entender y además existe el riesgo de que queden sutilmente mal
      Peor aún: si reutilizas la abstracción del código que estás probando, el test puede equivocarse de la misma forma que ese código
      Además, a diferencia del código de aplicación, los tests prácticamente se “componen” gratis
      Si no destrozaste seriamente el test harness, puedes agregar o borrar tests sin afectar a los demás, y como no hay fricción de integración, también hay una razón menos para evitar la duplicación
    • De acuerdo
      En pruebas he visto que esto se expresa como DAMP: “Descriptive and Meaningful Phrases”, un principio que enfatiza la legibilidad por encima de la singularidad
      Este principio puede generar duplicación en forma de código parecido repetido, pero hace que los tests se vean más obviamente correctos
      https://testing.googleblog.com/2019/12/…
      En la comunidad de Go hay una idea parecida: “una pequeña copia es mejor que una pequeña dependencia” https://go-proverbs.github.io/
    • La primera vez que leí Repeat yourself, do more than one thing, and rewrite everything, realmente me llegó
    • La parte de que intuitivamente sonaba incorrecto, en realidad nunca fue incorrecta desde el inicio
    • Viendo a Andrew Kelley programar aprendí una buena lección
      Le agradezco que comparta sus sesiones de live coding
      Si recuerdo bien, al empezar algo a menudo buscaba el código más parecido a lo que quería hacer, lo copiaba completo y luego lo modificaba desde ahí
      Yo pensaba: “¿no te vas a sentar un rato a pensar cuál es la abstracción compartida entre ambos?”, pero él simplemente seguía avanzando con copiar y pegar y era mucho más productivo que yo
  • Es interesante pensar en el momento del reescrito con IA de Zig → Rust de Bun https://xcancel.com/jarredsumner/status/2053063524826620129#m

    • De los últimos 150 PR fusionados en Bun, 108 estaban relacionados con seguridad de memoria, con problemas como olvidar limpiar en rutas de error, use-after-free, lecturas no inicializadas, accesos fuera de rango y reentrada
      Dice que 75 de ellos no habrían compilado en un lenguaje con destructores, semántica de movimiento y borrow checker
      Básicamente, uno de cada tres PR desplegados era “se nos olvidó liberar algo en una ruta de error”
      Unos 88 de los 108 estaban en Zig, y de los ~14 del lado de C++, la mayoría eran categorías residuales que seguirían existiendo en cualquier lenguaje, como ciclos de referencias y carreras de concurrencia con GC
      Así que la diferencia Zig→Rust es real, y los bugs de Zig son exactamente del tipo que se corrige con destructores y ownership, mientras que el lado de C++ ya está cerca del piso
      Sin garantías más fuertes en tiempo de compilación, esto seguirá siendo un juego del gato y el ratón
      La propuesta es dejar de arreglar una por una la categoría de bugs más grande y eliminarla estructuralmente
      bun/docs/rust-rewrite-plan.md at claude/phase-a-port · oven-sh/bun · GitHub
  • No me queda claro qué quiere decir el autor con la parte de que “en el 5% restante de los casos, no tener comptime es doloroso, y la única forma de llegar reliably a un resultado equivalente es la generación de código”
    Porque no dice nada sobre las macros procedurales

    • El comptime de Zig es realmente genial, pero el sistema de macros de Rust tampoco es algo que se pueda ignorar
      Es un poco engorroso construirlo bien, pero se pueden hacer muchas cosas
      También creo que la generación de código a veces carga con mala fama injustificada
      Con scripts build.rs he resuelto con generación de código bastantes problemas fastidiosos, y ha funcionado bien
      Claro, quizá después me arrepienta
  • El argumento central del artículo se ve más o menos así

    1. En un caso específico de personalizar allocators por estructura de datos, el costo de copiar y pegar código era un problema difícil
    2. Algunas funciones del sistema de tipos de Rust son más convenientes. Aun así, me cuesta creer que en ese ejemplo no pudieras pedirle a un agente que porte el diseño de tipos de Rust a Zig y obligue con comptime la forma de la API
    3. En funciones como bitflags o SoA, la legibilidad del código no importa tanto
    4. Si la compilación garantiza la ausencia de errores de seguridad de memoria, puedes revisar “100 veces” más código
      Rust sí es un buen lenguaje, pero aun así esto me parece exagerado
  • Parece publicidad de agentes de código

    • Parece una reflexión honesta sobre qué impacto tienen los agentes de código en la elección de lenguaje de programación
    • O tal vez sea publicidad con psicología inversa para Zig…
  • Esto viene de un artículo enlazado del mismo autor: un error muy común en programación gráfica es confundir los espacios de coordenadas, y el sistema de tipos es lo bastante potente como para expresar en tipos qué espacios de coordenadas y transformaciones son válidos
    Lo mismo aplica a moneda, unidades SI frente a distancias y pesos del sistema imperial, cadenas validadas frente a cadenas provistas por el usuario y secretos, etc.
    Además, si se gestiona bien, con tipos de estado se pueden impedir estados imposibles o no sound
    Pero personalmente, lo que más quiero de Rust es la eliminación completa de las carreras de datos
    Los lenguajes administrados también tienen carreras de datos
    En “solo usa Go”, en Go todo es mutable por referencia entre hilos, además con gimnasia de slices incluida
    Incluso JavaScript, que antes era un lenguaje totalmente de juguete y estaba entre los más puros y seguros, tiene en cada await una carrera potencial
    Y eso sin entrar en la maldad del patrón everything-is-an-EventEmitter
    Así que sí. Si tan solo tuviera GC… 🤫

  • Siento que está ocultando un poco el punto clave
    Los agentes de código también manejan Python y JavaScript muy bien
    Si son mejores que Rust ya entra en terreno subjetivo, pero aun así no elegiría esos lenguajes para muchos trabajos
    Me pregunto si el problema es que las funciones de Zig cambian con frecuencia, o si simplemente, por ser un lenguaje más nuevo, sus datos de entrenamiento de IA están más contaminados

  • Siento que Zig es más difícil de escribir que Rust, pero más fácil de leer
    En la era de la IA, uno lee más código del que escribe, así que yo termino prefiriendo Zig

  • Decir que Rust es más atractivo con la cantidad de código que se está generando ahora es apenas el primer paso
    Cuanto más código escriban las computadoras, más ventajosos serán los lenguajes más formales
    Esto parece otra fase del debate sobre tipos dinámicos
    Algo como: “los tipos dinámicos son más fáciles para los humanos, así que ¿por qué tendría que especificar lo mismo tres veces para las máquinas?”
    Tipos, lifetimes… ¿qué otras cosas hacen que sea más fácil para las máquinas escribir y consumir código?
    Me pregunto qué tan difícil se volverá para los humanos programar directamente en los lenguajes en los que las computadoras escribirán código