6 puntos por GN⁺ 2025-09-27 | 3 comentarios | Compartir por WhatsApp
  • En gestión de memoria, Zig ofrece un enfoque más simple e intuitivo que Rust
  • El borrow checker de Rust es poderoso, pero para desarrollar herramientas CLI pequeñas genera una complejidad excesiva y una carga para el desarrollador
  • La gestión manual de memoria en Zig permite lograr una buena seguridad de memoria con herramientas adecuadas y un poco de disciplina por parte del desarrollador
  • La seguridad del programa no depende solo de la seguridad de memoria; también importan factores como comportamiento predecible, rendimiento manejable y protección de datos
  • Rust es adecuado para sistemas grandes, pero para herramientas CLI pequeñas y prácticas, Zig resulta más ventajoso en productividad y mantenimiento

Resumen general

Últimamente, al crear herramientas CLI, he estado eligiendo Zig antes que Rust

Fundamentos de la gestión de memoria: stack y heap

  • El stack es una región de memoria rápida y de tamaño fijo que almacena datos muy temporales, como parámetros de funciones, variables locales y direcciones de retorno
  • El heap es el área para asignación dinámica de memoria, y se usa cuando la vida útil de los datos es larga o su tamaño se decide en tiempo de ejecución
  • El stack es estructuralmente simple, pero tiene espacio limitado; el heap, en cambio, requiere más atención en términos de velocidad y fragmentación

El Borrow Checker de Rust

  • El borrow checker de Rust garantiza la seguridad de memoria en tiempo de compilación
  • Impone reglas sobre referencias, propiedad y tiempos de vida (lifetime), evitando de antemano errores como desreferenciación de punteros nulos o punteros colgantes
  • Sin embargo, la seguridad de memoria solo se verifica en compilación y no elimina por sí sola los errores del usuario ni los problemas de diseño complejos relacionados con la propiedad

Caso práctico: mi propio Notes CLI

  • Al intentar escribir en Rust una CLI para gestionar notas personales, tuve que rediseñar a la fuerza la estructura por culpa del borrow checker
  • En cambio, en Zig, usando solo allocators, pude crear índices basados en punteros y hacer cambios o eliminaciones con mucha más simplicidad
  • El borrow checker de Rust tiene un propósito claro, pero con Zig, solo con conocimientos básicos de memoria y algo de disciplina, es posible alcanzar un alto nivel de eficiencia y seguridad

La seguridad de una herramienta CLI no se reduce a la seguridad de memoria

  • La seguridad real de un producto incluye muchos elementos: comportamiento predecible, retroalimentación útil ante errores, protección de datos sensibles y resistencia a ataques
  • Ni Rust ni Zig pueden considerarse realmente “seguros” si no cumplen también condiciones más allá de la seguridad de memoria
  • Por ejemplo, si una CLI sobrescribe datos en silencio ante un error o configura mal los permisos de archivos, el usuario puede enfrentar problemas graves
  • Seguridad en herramientas CLI

    • Comportamiento predecible: debe garantizarse una conducta consistente y clara incluso ante entradas inválidas o situaciones inesperadas
    • Prevención de fallos y corrupción de datos: los errores deben manejarse con elegancia, evitando corrupción de datos o cierres inesperados no reportados
    • Gestión del rendimiento: incluso al procesar grandes volúmenes de datos, no debería haber consumo excesivo de recursos ni pérdida de capacidad de respuesta
    • Protección de información sensible: hay que prestar atención a archivos temporales y configuración de permisos
    • Resistencia a ataques: se requiere robustez frente a validación de entradas, overflows de memoria y ataques de inyección

Fortalezas y límites del Borrow Checker de Rust

  • Fortalezas

    • Bloqueo de data races y referencias duplicadas: el compilador garantiza la regla de una sola referencia mutable o múltiples referencias inmutables
    • Fuertes garantías en compilación: la mayoría de los bugs relacionados con memoria se bloquean antes de ejecutar el programa
    • Detección temprana de bugs: esto supone una gran ventaja en servicios comerciales o sistemas concurrentes
  • Límites e incomodidades

    • Sobrecarga cognitiva: incluso en tareas pequeñas de CLI, es inevitable pensar en propiedad, tiempos de vida y manejo de referencias
    • Boilerplate/distorsión estructural: wrappers como Rc y RefCell, abuso de clone, rediseños de estructura, etc., hacen que uno se enfoque más en “satisfacer al compilador” que en “resolver el problema”
    • Poca ayuda ante bugs lógicos o de estado: solo garantiza reglas de memoria; no garantiza previsibilidad, errores lógicos ni integridad de datos
    • Complejidad en edge cases: en cachés, estado global o índices mutables, los conflictos de lifetime aparecen con facilidad
  • Como resultado, en proyectos CLI pequeños, el borrow checker de Rust puede convertirse en un “impuesto mental” para el desarrollador y volver todo más complejo de lo realmente necesario

El enfoque de Zig hacia la seguridad y la simplicidad

  • Zig se basa en comprobaciones de seguridad opcionales y gestión manual de memoria
  • Integra el concepto de allocator, lo que permite implementar un uso de memoria estructurado y predecible
  • También se pueden crear allocators personalizados para definir una estrategia de memoria acorde a las características del proyecto
  • Gracias a la sintaxis defer de Zig, la liberación automática y la limpieza de recursos al salir de un scope resultan mucho más intuitivas
  • A diferencia de Rust, se enfatiza la responsabilidad del desarrollador, por lo que hace falta disciplina, pero si se diseña bien la estructura, es fácil alcanzar y mantener la seguridad de memoria
  • Zig produce código conciso, y modificar estructuras con punteros, listas e índices es mucho más simple que en Rust
  • Es posible implementar código con el mismo nivel de seguridad y eficiencia sin sentirse restringido como en Rust
  • Además, la función comptime de Zig ayuda mucho con ejecución de código en compilación, pruebas y optimización

La importancia de la experiencia del desarrollador (Developer Ergonomics)

  • La experiencia del desarrollador (ergonomics) abarca factores como sintaxis del lenguaje, tooling, documentación y comunidad
  • Rust garantiza en última instancia la seguridad de memoria gracias a reglas muy estrictas, pero ese exceso de reglas y ceremony puede reducir la productividad
  • Zig enfatiza un diseño guiado por el desarrollador, lo que permite escribir, modificar y entender código con más facilidad y rapidez
  • Zig, con su código intuitivo, iteración rápida y baja carga mental, permite que el desarrollador se concentre en resolver problemas sin pelearse con la herramienta
  • Zig confía en el desarrollador y le da herramientas y capacidad de elección adecuadas, mientras que Rust puede sentirse excesivamente supervisor y restrictivo
  • Un entorno amigable para desarrolladores no consiste en “protegerlos de cometer errores”, sino en garantizarles la oportunidad de aprender y mejorar a través de sus propios errores

Conclusión

  • En áreas donde las ventajas de Rust se maximizan —como sistemas grandes, multihilo y de larga ejecución—, Rust sigue siendo la mejor opción
  • Sin embargo, para herramientas CLI pequeñas y prácticas, la ligereza, simplicidad y rapidez de implementación y mantenimiento de Zig son una mejor opción
  • La seguridad de memoria es solo una pieza del rompecabezas de la seguridad, y elementos esenciales para una CLI —como comportamiento predecible, mantenibilidad y robustez— se logran más fácilmente en Zig
  • Al final, lo importante no es cuál es el “mejor lenguaje”, sino elegir la herramienta adecuada para mi flujo de trabajo y las características del proyecto
  • Zig es un lenguaje que encaja perfectamente en el desarrollo de herramientas pequeñas al combinar “seguridad de memoria + bajo costo mental + afinidad con el desarrollador/productividad”

3 comentarios

 
shakespeares 2025-10-05

Creo que el ecosistema todavía no parece estar tan consolidado como el de Rust.

 
bus710 2025-09-27

Como Zig tiende a tener cambios que rompen compatibilidad con bastante frecuencia en las versiones nuevas..., incluso en proyectos pequeños conviene agregar CI y darles mantenimiento de forma continua.

 
GN⁺ 2025-09-27
Opinión en Hacker News
  • La ventaja de Zig es que te permite seguir pensando como desarrollador de C, aunque hasta cierto punto eso también es simplemente una cuestión de costumbre

    • Los desarrolladores que ya están lo bastante familiarizados con Rust dejan de pelearse con el borrow checker; simplemente ya piensan con esa estructura de código

    • En Rust, un enfoque tipo “object soup” no funciona muy bien, pero tampoco creo que eso sea una forma intrínsecamente más fácil; solo se siente más fácil porque estamos acostumbrados a ella

    • Si aceptamos la premisa de que la ergonomía es difícil de medir o cuantificar, este tipo de discusión se vuelve inevitablemente ambiguo

      • Se parece a decir: “esta silla no se va a romper, así que siéntate tranquilo; quizá sea un poco menos cómoda y más pesada, pero la mayoría se acostumbrará pronto y dejará de notarlo”
      • Como dice el texto original: “Rust ofrece seguridad de memoria a costa de una experiencia de desarrollo incómoda, mientras que Zig puede ofrecer una mejor experiencia de desarrollo y seguridad de memoria con un poco de cuidado”; al final eso es un intercambio entre seguridad y usabilidad
      • Creo que la comunidad de Rust debería reconocer con honestidad ese trade-off; conseguir seguridad siempre implica menos comodidad
      • Eso aplica en seguridad, en la vida, en lo cotidiano y en el software en general, aunque muchas veces se expresa con argumentos ambiguos o subjetivos
      • También hay quienes descartan con facilidad el punto de la ergonomía diciendo “para la gente acostumbrada no hay problema”, y eso puede sonar como “si hasta eso te cuesta, entonces eres menos inteligente”
    • La idea de “pelearse con el borrow checker” viene de la época en que Rust solo entendía lifetimes léxicos

      • Cuando yo aprendí Rust en 2021, eso ya era una historia del pasado
      • Aun así, en la práctica, adaptarse a Rust puede no ser fácil para quienes solo han usado Python, C o JavaScript
      • En mi caso me resultó natural, aunque no parece ser lo habitual
      • Pero decir “en Rust tienes que leer los mensajes de diagnóstico y corregir tu código” no suena tan épico como “luchar valientemente contra el borrow checker”, y creo que la situación real debería describirse con más honestidad
    • En mi experiencia, los desarrolladores expertos de Rust terminan poniendo Arc por todas partes y lo usan casi como si fuera garbage collection automático

      • La gestión estática de memoria es bastante estricta, así que en estructuras de datos complejas se vuelve difícil en la práctica
      • Los lifetimes hacen que los caminos que hay que seguir superen con facilidad lo que una persona puede manejar mentalmente
    • También he visto muchos proyectos open source en Rust donde incluso desarrolladores experimentados usan Arc, Clone, Copy, etc. por todos lados

    • La ventaja de Zig es que permite desarrollar de una forma familiar, como en C, pero con funciones del lenguaje y del tooling que ayudan con la seguridad

      • Por ejemplo, los optional de Zig ayudan a evitar problemas de dereference de nil
      • Al depurar o probar, también puedes pasar directamente allocators de debug o personalizados para revisar en runtime el acceso a memoria y fugas de recursos
      • Me incomoda la falta de interfaces o traits explícitos, pero creo que su adopción es simple y por eso resulta práctica
  • No estoy de acuerdo con la mayor parte del texto original

    • Rust, al igual que C o Zig, también te obliga a pensar en lifetimes, ownership y borrow scope; la diferencia es si cuentas o no con ayuda del compilador

    • Por muy inteligente que seas, los humanos nos equivocamos cuando estamos cansados o distraídos; reconocerlo es precisamente ser sensato

    • El espacio de programas que el compilador de Rust considera seguros no es lo bastante amplio, así que con bastante frecuencia rechaza programas perfectamente razonables

    • Ejemplo: si en una estructura Foo, bar y baz son strings, e intentas tomar una referencia mutable a bar y luego una inmutable a baz, no compila, así que en situaciones así no queda más que forzar una reestructuración del código

    • Como contraargumento, tener que cambiar el código a un diseño de segunda o tercera opción solo para evitar casos donde “en realidad todo estaría bien, pero el compilador lo rechaza” ya es una carga importante

      • Eso puede terminar cambiando el diseño de toda la base de código
      • Y eso explica por qué adoptar Rust puede resultar pesado para gente que hace tareas difíciles, como desarrolladores de videojuegos
      • Si el compilador de Rust funcionara siempre con precisión perfecta y sin falsos positivos, sería una maravilla también en ergonomía, aunque claro, en la práctica eso no es nada fácil
    • Ese ejemplo de arriba me parece realmente excelente; me gustaría preguntar si podría tratarlo en mi blog o en un artículo

    • Después de ver ese código, de hecho me siento menos seguro de mi propia postura

  • Hay que recordar que no todos los programas necesitan ser “seguros” de esa manera

    • Muchos crecimos disfrutando software inseguro: Star Fox 64, MS Paint, FruityLoops, etc.

    • También leí que Andrew Kelley, creador de Zig, hizo Zig porque no tenía un buen entorno para desarrollar software de producción musical (DAW), y me parece que Zig encaja bien con ese tipo de software creativo

    • Si a alguien le preocupan mucho los bugs de memoria, siempre puede usar Rust

    • Incluso creo que Super Mario World fue más divertido gracias a sus bugs de memoria

    • “Seguro” es una forma abreviada de decir “mi programa funciona como yo pretendía”

      • El código no intencional y sin sentido semántico juega en contra de lograr ese objetivo
      • Hay casos donde uno escribe código deliberadamente críptico o extraño por motivos artísticos o técnicos, como IOCCC, hacking o poesía en código, pero en esos casos hay que hacerlo con mucho cuidado
      • En Rust también puedes implementar eso a propósito usando una escape hatch como unsafe
      • El argumento del texto parece ser que escribir código sin sentido de forma involuntaria es una ventaja, y eso me cuesta mucho aceptarlo
      • Si se pudiera volver seguro todo el código sin ninguna desventaja, ¿quién no querría eso?
      • En cosas como los speedruns de Super Mario World, también podría hacerse con parches binarios; no creo que manipular memoria mediante input sea la única fuente de diversión
    • Estoy un poco confundido: me pregunto si la razón por la que creen que mi comentario es malo es porque da a entender que la seguridad de memoria no importa

  • Me pareció una lástima que se subestimara el valor del borrow checker

    • El borrow checker de Rust garantiza en tiempo de compilación que no habrá accesos inválidos a memoria

    • Claro, eso viene con la incomodidad de tener que reestructurar el código para ajustarse a las reglas del compilador

    • Cuando he usado Rust por mi cuenta, nunca sentí que las lifetime annotations estuvieran mal; sí parecían una tarea molesta, pero me acostumbré rápido

    • A menos que uses unsafe, en Rust no puedes tener dos threads escribiendo al mismo tiempo sobre la misma memoria

    • No me convence la idea de “por qué Zig se siente más práctico para herramientas CLI”; Rust sigue teniendo ventajas claras para prevenir CVE y otras vulnerabilidades

    • En la práctica, la mayoría del trabajo que hago podría hacerse perfectamente en lenguajes con GC, y cuando contribuyo en otros lenguajes, me da igual si es Rust, Zig o C/C++

    • ¿Que las herramientas CLI no tienen nada de especial?

      • En general, una herramienta CLI no suele ser tan grande o muchas veces la desarrolla una sola persona, así que es más fácil de manejar
      • Zig o C quizá no encajen tan bien en bases de código enormes; en proyectos más complejos hace falta una especie de babysitter
      • Hubo un debate parecido en el pasado con Java, al que también llamaban un “lenguaje babysitter”, pero en muchas bases de código eso es justo lo que hace falta
    • Lo de que, sin unsafe, dos threads no pueden escribir al mismo tiempo en la misma memoria no es tan limpio como suena

      • El apoyo del compilador que yo quiero está más relacionado con resolver problemas de memory ordering
      • En ese terreno, Rust puede clasificar ciertas race conditions como safe aunque ocurran sin unsafe
  • Sí coincido en que implementar backlinks en Rust es demasiado complicado

    • Se puede hacer con Rc, Weak, RefCell, .borrow(), etc., pero no es sencillo

    • Si es un programa de vida corta, también se puede usar asignación por arena, que parece encajar con lo que se entiende aquí por herramienta CLI

    • Rust realmente brilla en aplicaciones enormes, multihilo y de larga duración

    • Yo mismo escribí un cliente grande de metaverso en Rust, y aun con decenas de threads corriendo 24/7 no tuve fugas de memoria ni crashes

    • Para hacer lo mismo en C++ necesitaría un equipo de QA y herramientas como Valgrind; con lenguajes de scripting el rendimiento sería demasiado bajo

    • Yo también hice en Rust un avión de simulación física que incluso consideraba la curvatura de la Tierra y variaciones de gravedad

      • Corrí durante años pruebas de rutas de 5 y 22 horas sin ningún problema
      • En 7 años usando Rust solo he tenido unos pocos crashes; C/C++ los uso únicamente cuando me toca arreglar código heredado
  • Zig es atractivo, pero D sigue existiendo, y personalmente siento que D es el reemplazo de C/C++ que yo querría

    • La sintaxis de Zig se me hace algo rara, y Rust ya ocupa una posición central en el ecosistema

    • Go también tiene mucha presencia en herramientas y, en IA, es probablemente el lenguaje más usado después de Python

    • Antes de Rust, el debate era Go vs. D, y yo hasta llegué a comprar un libro de D antes de pasarme a Go

      • Para mí, su biblioteca estándar era mucho más práctica, y nombres de tipos como int64 me parecían más intuitivos
    • D está bien, pero nunca apareció una killer app que lo masificara

  • No termino de ver por qué habría que usar Rust o Zig específicamente para herramientas CLI

    • El cuello de botella suele ser el IO, no la lentitud del GC

    • Salvo en cosas intensivas en memoria como juegos o bases de datos, no creo que el GC sea realmente el punto central

    • Más que discutir seguridad de memoria, me interesa pensar por qué uno querría elegir un lenguaje sin GC

    • Si la razón es “porque programar sin GC es divertido”, eso ya es razón suficiente y no hace falta debatirlo

    • Un startup time inmediato, sin retraso al arrancar, sí puede ser muy útil

      • Si haces herramientas wrapper, puedes ejecutarlas miles de veces sin que moleste
      • Además, como no tienes la complejidad de despliegue de un entorno tipo Python, distribuirlas también es más fácil
    • Me fue bastante bien haciendo CLI en Go, aunque no me guste mucho el lenguaje en sí

      • Las CLI en Python se vuelven tediosas de distribuir cuando tienen muchas dependencias, y Rust/Zig también son populares porque, como Go, permiten distribuir binarios estáticos con facilidad
    • Mi primera opción son lenguajes con sum types, pattern matching y soporte async

      • Incluso si no es Rust, me gusta mucho cualquier función que ayude a detectar errores en tiempo de compilación
    • Sobre la idea de que trabajar sin GC solo importa en juegos

      • En la práctica, muchos juegos móviles también usan GC, como en entornos Unity + il2cpp, y además el rendimiento del GC no siempre es bueno
    • El debate sobre GC a veces se siente como puro bandwagon

      • Hace 50 años ya existían grandes workstations construidas con éxito sobre lenguajes con GC, como Interlisp o Cedar
      • El hardware de hoy es muchísimo mejor que el de las CLI de los años 70 o que muchas apps Electron, pero no lo estamos aprovechando de forma eficiente
  • Hice una herramienta simple de notas en Rust usando borrow/referencing incorporados, y no fue tan complejo como esperaba

    • Si imaginas una estructura donde guardas índices dentro de una lista de notas y los conectas con un mapa, casi no hay diferencia de velocidad y tampoco hay desventajas de seguridad

    • Incluso si te equivocas con un índice, te topas con un error por estar fuera de rango, lo cual sigue siendo muchísimo mejor que sobreescribir memoria del kernel

    • Hasta para hacer debugging con printf resulta mucho más fácil e intuitivo

    • Los raw pointers o references normalmente los reservaría para sitios donde de verdad se necesitan, como un allocator o un async runtime; para la lógica común, un enfoque basado en índices suele encajar mejor

    • De hecho, una razón muy conocida de los problemas relacionados con Pin en el async de Rust es justamente que no puedes usar self-referential structs

    • Un puntero a un valor almacenado en un vec queda inválido si ocurre un realloc, y en ese caso Miri te lo marca de inmediato como error

  • Como desarrollador de C++, si buscara un lenguaje seguro, sentiría que Swift sería la opción más adecuada

    • Adaptarte es más fácil cuando el lenguaje te resulta familiar o se parece a algo que ya conoces

    • Swift ha mejorado mucho su soporte cross-platform últimamente, y varias personas del comité de estandarización de C++ participan activamente en él

    • Pero por su asociación con Apple y la falta de frameworks de UI nativos fuera de ese ecosistema, su expansión en el mundo no Apple ha sido más limitada

    • Ojalá Swift gane más popularidad

    • Si alguien tiene recursos para comparar Swift con Zig o C, agradecería recomendaciones

  • Se dice que Zig también puede usarse para hacer software con seguridad de memoria si se trabaja con un nivel razonable de cuidado, pero en realidad con C también puedes obtener algo parecido si eres suficientemente disciplinado

    • El problema es que, en la práctica, ese “poquito de disciplina” suele faltar, y ahí es donde aparecen los problemas

    • Zig además resuelve varios problemas adicionales

      • Accesos fuera de rango (que representan el 70% de todos los CVE)
      • null pointer dereference
      • Seguridad de tipos
      • Está muy por encima de C y, mientras no cruces ciertos límites, errores como use-after-free también son mucho más fáciles de evitar
      • También destacan su excelente sistema de compilación cross-platform, las optimizaciones con comptime y tiempos de build decenas de veces más rápidos que en C++ o Rust
      • Aún le falta madurez en la biblioteca estándar y quedan detalles menores por resolver, pero creo que tiene mucho futuro para programas orientados al rendimiento
    • Si C ha fracasado durante más de 50 años con este problema de disciplina, entonces estamos hablando de algo más difícil que el camino de un monje Shaolin