- 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
RcyRefCell, abuso declone, 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
lifetimeaparecen 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
deferde 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
Creo que el ecosistema todavía no parece estar tan consolidado como el de Rust.
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.
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
La idea de “pelearse con el borrow checker” viene de la época en que Rust solo entendía lifetimes léxicos
En mi experiencia, los desarrolladores expertos de Rust terminan poniendo
Arcpor todas partes y lo usan casi como si fuera garbage collection automáticoTambién he visto muchos proyectos open source en Rust donde incluso desarrolladores experimentados usan
Arc,Clone,Copy, etc. por todos ladosLa 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
nilNo 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,barybazson strings, e intentas tomar una referencia mutable abary luego una inmutable abaz, no compila, así que en situaciones así no queda más que forzar una reestructuración del códigoComo 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
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”
unsafeEstoy 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 memoriaNo 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?
Lo de que, sin
unsafe, dos threads no pueden escribir al mismo tiempo en la misma memoria no es tan limpio como suenasafeaunque ocurran sinunsafeSí coincido en que implementar backlinks en Rust es demasiado complicado
Se puede hacer con
Rc,Weak,RefCell,.borrow(), etc., pero no es sencilloSi 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
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
int64me parecían más intuitivosD 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
Me fue bastante bien haciendo CLI en Go, aunque no me guste mucho el lenguaje en sí
Mi primera opción son lenguajes con sum types, pattern matching y soporte async
Sobre la idea de que trabajar sin GC solo importa en juegos
El debate sobre GC a veces se siente como puro bandwagon
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
printfresulta mucho más fácil e intuitivoLos 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
Pinen el async de Rust es justamente que no puedes usar self-referential structsUn puntero a un valor almacenado en un
vecqueda inválido si ocurre unrealloc, y en ese caso Miri te lo marca de inmediato como errorComo 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
null pointer dereferencecomptimey tiempos de build decenas de veces más rápidos que en C++ o RustSi 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