1 puntos por GN⁺ 2025-01-08 | 1 comentarios | Compartir por WhatsApp
  • La metaprogramación en tiempo de compilación (comptime) de Zig puede leerse como código de runtime, pero aun así generar código ejecutable tan eficiente como una función en C escrita a mano para tareas repetitivas como sumar campos de una estructura
  • Un caso de uso clave de Zig comptime es que maneja la programación genérica usando comptime T: type y anytype en lugar de una sintaxis separada para genéricos
  • Runtime, comptime y el sistema de build comparten el mismo lenguaje Zig, por lo que incluso una función como Fizz Buzz puede ejecutarse durante la compilación y convertirse en un arreglo precalculado
  • El compilador evalúa primero todo lo posible y deja solo las partes que requieren valores de runtime como bytecode de salida que más tarde se convertirá en código máquina
  • Zig comptime puede resolver problemas similares a la generación de código por texto, pero no reemplaza palabras clave arbitrariamente como los macros de C, lo que reduce la carga de lectura y depuración del código

Una forma de leer Zig comptime

  • El comptime de Zig es una función de metaprogramación, pero al leer el comportamiento final no hace falta ser demasiado consciente de la diferencia entre tiempo de compilación y runtime
  • El tercer punto de zig zen, “Favor reading code over writing code”, se vuelve un criterio importante
    • La metaprogramación basada en macros o generación de código puede crear dos capas: el código original y el código expandido
    • Ese tipo de capa indirecta dificulta leer el código, depurarlo y cambiar su comportamiento
  • Se compara lado a lado el código de runtime que suma un arreglo [3]i64 = .{1,2,3} y el código comptime que suma los campos a, b, c de la estructura MyStruct
    • Se recorren los nombres de campo con inline for (comptime std.meta.fieldNames(MyStruct))
    • Se accede a los campos usando nombres conocidos en tiempo de compilación con @field(my_struct, field_name)
  • El ejemplo de sumar campos de una estructura puede parecer reflexión en runtime, pero en el ejecutable queda código eficiente para ese tipo de estructura, como si la función de suma se hubiera escrito a mano
  • El proceso de transformación del ejemplo no es una reproducción exacta de la implementación de Zig, sino una explicación conceptual
  • El equipo central de Zig está trabajando en un depurador que permita ejecutar paso a paso código mezclado entre comptime y runtime como en el ejemplo

Los genéricos se resuelven con comptime

  • Zig no tiene una función separada de genéricos
  • Para hacer genérico un tipo, se escribe una función que recibe un tipo y devuelve un tipo
    • Ejemplo: pub fn GenericMyStruct(comptime T: type) type
    • Los campos a, b, c del struct devuelto usan todos el tipo T
  • Las funciones genéricas también pueden escribirse del mismo modo
    • Ejemplo: fn quadratic(comptime T: type, a: T, b: T, c: T, x: T) T
    • Se pueden indicar argumentos de tipo explícitos como quadratic(f32, ...) y quadratic(i64, ...)
  • Si el tipo de un argumento no es importante para otras partes de la firma de la función, puede inferirse con el tipo especial anytype
  • Esto por sí solo no explica todo comptime, pero sí es un buen punto de partida para entender tareas genéricas comunes en Zig

Ejecutar el mismo código Zig en tiempo de compilación

  • Zig usa el mismo lenguaje en runtime, comptime y el sistema de build
  • En el ejemplo de Fizz Buzz, primero se escribe la función fizzBuzz(writer) como una función normal de salida en runtime
    • Recorre del 1 al 100
    • Imprime fizzbuzz en múltiplos de 3 y 5, fizz en múltiplos de 3, buzz en múltiplos de 5, y el número en los demás casos
  • Al ejecutar la misma función fizzBuzz dentro de un bloque comptime, se puede precalcular toda la salida
    • Primero se calcula cuántos bytes se necesitan con std.io.countingWriter(std.io.null_writer)
    • Con esa longitud calculada se crea el arreglo buffer: [cw.bytes_written]u8
    • Luego se vuelve a escribir sobre std.io.fixedBufferStream(&buffer) para producir el valor full_fizzbuzz
  • Si se mide solo la parte clave, la versión precalculada corre alrededor de 9 veces más rápido
    • Como el ejemplo es pequeño, el tiempo total de ejecución depende más de otros factores
  • Entre comptime y runtime hay diferencias en los valores y funciones accesibles
    • Los valores comptime_int, comptime_float y type solo están disponibles en comptime
    • Algunas funciones aceptan solo argumentos comptime, así que en la práctica son exclusivas de comptime
    • Las llamadas al sistema o funciones que dependen de ellas solo están disponibles en runtime
  • El código que no usa estas capacidades exclusivas se comporta igual en comptime y en runtime

Entender comptime como evaluación parcial

  • Comptime puede verse como evaluación parcial (partial evaluation) que ocurre durante la compilación
  • La evaluación parcial consiste en tomar una función con solo algunos argumentos dados, sustituir y calcular primero las expresiones que usan valores ya conocidos, y producir una nueva función que recibe únicamente los argumentos que siguen siendo desconocidos
  • El ejemplo de suma de arreglo muestra el proceso de evaluación paso a paso
    • El bucle for se descompone en sentencias individuales por cada iteración
    • array[0], array[1], array[2] se sustituyen respectivamente por 1, 2, 3
    • sum se actualiza como 0 → 1 → 3 → 6
    • Al final, todo se simplifica hasta una forma como std.debug.print(..., .{6})
  • El ejemplo de suma de campos de estructura puede leerse del mismo modo
    • Como inline for se ejecuta en comptime, se expande en sentencias individuales para los campos "a", "b", "c"
    • @field(my_struct, "a") cambia a my_struct.a
    • La función final queda con una forma que contiene directamente sum += my_struct.a, sum += my_struct.b, sum += my_struct.c
  • Este enfoque permite expresar la intención directamente en el código y reduce la necesidad de dejar comentarios que digan que la función de suma debe actualizarse manualmente cuando cambien los campos

Evaluación en tiempo de compilación y emisión de código de runtime

  • Zig comptime también puede verse como una combinación de evaluación en tiempo de compilación y emisión de código de runtime
  • El código de entrada es ejecutado por el compilador
    • Las sentencias que pueden conocerse en tiempo de compilación se evalúan de inmediato
    • Las sentencias que necesitan valores de runtime se agregan al código de salida
  • Esta perspectiva es la más cercana a cómo realmente procesa las cosas el compilador de Zig
    • Zig primero analiza la sintaxis
    • Convierte el código en bytecode para una máquina virtual
    • La VM evalúa las partes posibles y emite nuevo bytecode para las partes que requieren procesamiento en runtime
    • Ese nuevo bytecode luego se convierte en código máquina
  • Las condicionales como if con entradas de runtime emiten ambos caminos
  • El código muerto no pasa por análisis semántico
    • Incluso si se escribe una función incorrecta, puede que no aparezca un error de compilación hasta que realmente se use
    • Esta característica hace la compilación más eficiente y permite compilación condicional natural sin #ifdef
  • Los nombres de tipo en Zig también son expresiones que evalúan valores type en comptime
    • Incluso un nombre de tipo que parece simple, como el tipo de un argumento de función, es el resultado de una evaluación en comptime
    • Esta propiedad hace posible el ejemplo de genéricos anterior
    • Si hace falta, el tipo puede calcularse con expresiones más complejas
  • El análisis estático de Zig es más complejo que el de muchos otros lenguajes estáticos tipados
    • Para averiguar todos los tipos, el compilador debe ejecutar una parte considerable de sí mismo
    • Hasta que las herramientas se pongan al día, funciones del editor como el autocompletado no siempre funcionarán bien

Relación con la generación de código por texto

  • Zig comptime no tiene la misma forma que la generación de código por texto, pero puede resolver problemas parecidos
  • La función generadora writeSumFn produce el código fuente de la función sumFields con writer.print
    • El código que se ejecuta dentro del generador corresponde a la parte comptime de Zig
    • El código que produce el generador corresponde al código de runtime
  • La versión con comptime de Zig expresa la misma lógica de forma más directa
    • Obtiene los nombres de campo con std.meta.fieldNames(MyStruct)
    • Construye el acceso a campos con inline for y @field
  • La generación de código que usa información de tipos como entrada se vuelve más simple en Zig
    • En el enfoque de generación de texto, hay que resolver por separado de dónde salen los nombres de tipos y de campos
    • En Zig, el propio tipo y la información de sus campos pueden manejarse directamente en comptime
  • Cuando se usan entradas como una especificación externa, se puede incluir el contenido del archivo con @embedFile y luego parsearlo como de costumbre
  • En el ejemplo de estructura genérica, comptime maneja las referencias a nombres de tipo de manera más directa que la generación de código por texto
    • La generación de texto tiene que concatenar cadenas como MyStruct_{s} y mantener la consistencia
    • Comptime usa directamente una función como GenericMyStruct(T)
  • También hay excepciones
    • Es posible crear un tipo cuyos nombres de campo se decidan en comptime
    • En ese caso hay que llamar una función builtin con una especificación que contenga la lista de definiciones de campos
    • En esos tipos no se pueden definir declaraciones como métodos
    • No limita la expresividad del código en sí, pero sí restringe la forma de la API que puede exponerse a otro código
  • La mayoría de las tareas razonables que pueden hacerse con macros de texto como los de C también pueden hacerse con comptime, aunque la forma del código puede ser muy distinta
  • Zig no ofrece una función para reemplazar palabras clave por otros nombres como hacen los macros de texto

Lecturas adicionales y ejemplos de la biblioteca estándar

  • Zig en sí no es un lenguaje que dependa solo de comptime, y se puede aprender más en el sitio web oficial
  • La referencia del lenguaje cubre las funciones concretas de comptime
  • La biblioteca estándar de Zig tiene ejemplos de uso de comptime
    • La función de formato que usa std.debug.print es una potente función genérica
      • En muchos lenguajes, la cadena de formato se analiza en runtime y se puede agregar un validador aparte para detectar errores antes
      • Zig analiza la cadena de formato en comptime para generar código de salida eficiente y hacer toda la validación en tiempo de compilación
    • ArrayList es un contenedor genérico relativamente simple pero con funcionalidad completa
    • La función main de Zig puede tener varios tipos de retorno, y esto no se resuelve con magia del compilador sino con código comptime común

1 comentarios

 
GN⁺ 2025-01-08
Opiniones de Hacker News
  • Me gustaría que se abordaran con más profundidad los problemas ya conocidos en la programación en tiempo de compilación, en vez de elogiarla sin espíritu crítico.
    La programación por etapas no es algo nuevo, y las familias de Lisp han hecho cosas similares durante décadas, pero en ese proceso salieron a la luz muchos compromisos de diseño y problemas. Por ejemplo, los genéricos de este estilo rompen la parametricidad (parametricity), lo que vuelve difícil razonar solo a partir de la firma de tipo de una función. Tampoco queda claro cómo maneja Zig los tipos genéricos recursivos, y también es una decisión importante cuándo y en qué orden interactúan la verificación de tipos y el cálculo en tiempo de compilación. El artículo dice que el código en tiempo de compilación puede generar no solo valores, sino también código, pero no aborda la higiene (hygiene). Una buena discusión relacionada es https://typesanitizer.com/blog/zig-generics.html

    • Me gusta bastante Zig y lo he seguido durante años, usándolo de vez en cuando, pero comptime brilla especialmente en casos como genéricos, inicialización en tiempo de compilación de estructuras de datos complejas y generación de código específica por destino.
      Dicho eso, en otras situaciones, cuando veo comptime en código Zig a veces pienso: “ah…”. Como con las macros de Lisp, es fácil usarlo para esquivar problemas que en realidad no existirían, o desaparecerían, si el código estuviera mejor estructurado. El ejemplo del artículo original, que recorre los campos de una estructura para sumar valores, es típico de cómo la gente usa comptime, pero en muchos casos es mejor usar una estructura de datos que realmente se pueda recorrer, como std.enums.EnumArray
    • Me pregunto si cuando dicen que se rompe la parametricidad se refieren a la capacidad de un programador práctico para entender el comportamiento de una función, o a razonar dentro de un sistema de teoría de tipos como el cálculo lambda tipado.
      En teoría, la preocupación es clara, pero otra cosa es qué tan importante resulta en un lenguaje práctico. Las plantillas de C++ también rompen la parametricidad mediante la especialización de plantillas, pero en la práctica no suele ser un gran problema y más bien permite optimizaciones. Por ejemplo, una implementación como std::vector<bool>, que almacena cada elemento realmente en 1 bit, solo es posible con ese tipo de flexibilidad
    • Como autor, escribí este artículo para ordenar lo que no logré explicar bien en una reunión, así que me enfoqué más en explicar que en criticar.
      Veo el comptime al estilo Zig como un compromiso directo entre capacidad de inferencia y expresividad frente a firmas de función definidas de forma más estática. Solo afecta al código que hace ese tipo de inferencias a partir de los tipos, y es algo que se puede elegir según la necesidad. Incluso cuando la firma de tipo no era suficiente, por lo general bastaba leer unas decenas de líneas de código fuente para entender cómo usarlo, y nunca me quedé confundido por mucho tiempo. En el ejemplo de tipos genéricos recursivos, usar el nombre que se está declarando produce “dependency loop detected”, pero hay formas de evitarlo, y el ejemplo genérico del artículo también se referencia a sí mismo. La verificación de tipos ocurre durante comptime; por ejemplo, el primer @compileLog("Hi") se ejecuta, pero si después const a: u32 = "42"; produce un error de tipo, no se llega al segundo log. Pueden aparecer problemas sutiles en la verificación de tipos entre comptime y runtime, pero se ven solo en código bastante rebuscado, se resuelven fácilmente, y el equipo principal los conoce, así que espero que se aborden antes de la 1.0. Necesitaría una explicación más precisa de qué significa exactamente higiene
    • Poder razonar solo mirando la declaración de una función es algo independiente de la ejecución en tiempo de compilación.
      Si una función tiene un propósito lógico claro y buenos nombres para la función y sus parámetros, se puede razonar a partir de la declaración. Las partes que el programador puede especificar, como las etiquetas de los parámetros y los nombres de tipos, también pueden verse como parte del nombre. Estoy de acuerdo con la conclusión del título del artículo enlazado, pero el texto se parece más a un ensayo de equipo de debate que busca ganar puntos que a un argumento significativo. Un mejor encuadre sería flexibilidad frente a complejidad. Un sistema fijo de genéricos, si está bien diseñado, es más simple que uno programable, pero menos flexible. La ventaja de Zig es que el lenguaje de metaprogramación es casi el mismo que el lenguaje normal, y ese lenguaje normal también es simple, lo que reduce el costo de complejidad adicional. Sin embargo, sí aparece una complejidad en la que se mezclan el código en tiempo de compilación y el código en runtime, con pocas pistas, lo que puede dificultar que el programador distinga qué se ejecuta y cuándo. Me pregunto si un servidor de lenguaje o un plugin de editor podría ofrecer un “sombreador de comptime” que dé otro color de fondo al código comptime
    • Me molesta que este tema se discuta a partir de una demo de 15 minutos y con poca comprensión de los compromisos reales. El artículo enlazado de Varun Gandhi es excelente.
      Por mi experiencia con Rust, muchas de las cosas que la gente intenta hacer con genéricos constantes serían más fáciles con una función como comptime. Permitir aritmética sobre genéricos constantes manteniendo la parametricidad es difícil de implementar, y si lo que realmente se quiere es algo como “un trait de función hash cuyo tamaño de salida sea N”, puede estar bien renunciar a la parametricidad para ese propósito y convertir el trait en N en una etapa previa de generación de código. Pero las macros de Rust son demasiado flexibles y molestas para usarse de esa manera. Aun así, en cuanto reemplazas el polimorfismo paramétrico por una función ingenua de generación de código, te estás metiendo en un camino difícil
  • D lo tenía desde hace 17 años. Las funciones de D se han ido trasladando de forma constante a otros lenguajes
    Zig marca con la palabra clave comptime los bloques que se ejecutarán durante la compilación, pero en D lo que decide la ejecución no es una palabra clave, sino si es una expresión constante. Por ejemplo, int s = sum(3, 4); se ejecuta en tiempo de ejecución, y enum e = sum(3, 4); se ejecuta en tiempo de compilación. Si se evitan variables globales no constantes, entrada/salida y llamadas a funciones del sistema como malloc(), una gran cantidad de funciones puede ejecutarse en tiempo de compilación sin cambios. Gracias a la gestión automática de memoria de D, también es posible asignar memoria

    • Uno de mis usos favoritos es la generación de tablas estáticas
      Antes había que escribir un programa aparte para crear tablas estáticas, pero con la ejecución de funciones en tiempo de compilación eso ya no es necesario. Los valores de inicialización del arreglo tytab salen de una lambda que calcula y devuelve el arreglo. El ejemplo completo está en https://github.com/dlang/dmd/blob/master/compiler/src/dmd/ba.... Otro uso común de CTFE es crear DSL
    • No quiero dejar pasar la oportunidad de agradecer que se haya creado D. Dicho eso, me intrigan los límites de la ejecución en tiempo de compilación
      Si la máquina donde corre el compilador y la máquina donde se ejecutará el programa son distintas, ¿cómo garantiza el compilador de D que el resultado sea correcto? Por ejemplo, me pregunto cómo sabe el compilador que int s = sum(100000, 1000000) tendrá el mismo valor en todas las máquinas x86. Podría haber diferencias sutiles entre generaciones de CPU; entonces, ¿cómo se garantiza que un cálculo hecho realmente en el host dé el mismo resultado en la máquina objetivo? ¿O se asume que, si la arquitectura es la misma, host y objetivo son lo suficientemente parecidos?
    • ImportC de D también permite hacer CTFE con código C
      Con int sum(int a, int b) { return a + b; }, se puede usar algo como _Static_assert(sum(3, 4) == 7, "look ma, check at compile time!");. No sé por qué el estándar de C no agrega esto, y funciona bien
    • Para ser justos, Zig también permite llamar la misma función tanto en un contexto de runtime como en un contexto comptime
      square(2) es una llamada en runtime, y comptime square(3) es una llamada en tiempo de compilación. Una llamada comptime produce un error de compilación si algo no es compatible con tiempo de compilación, y me parece una función importante. Sirve como señal de alerta cuando código que esperabas que se ejecutara en tiempo de compilación pasa accidentalmente a evaluarse en runtime por un cambio en los argumentos de entrada
  • Zig se ve interesante, pero me gustaría que tuviera sobrecarga de operadores
    La mayoría de los argumentos contra la sobrecarga de operadores no me convencen. El argumento de que no sabes qué está pasando realmente por dentro no basta, porque también podrías hacer que una función llamada add haga una multiplicación. iostreams de C++ o boost::spirit suelen citarse como casos de abuso, pero en otros lenguajes con sobrecarga de operadores no se ve tanto eso, y parece más bien un problema propio de C++

    • Creo que una solución al estilo OCaml encajaría bien con los usos de Zig
      En OCaml se pueden redefinir operadores, pero solo dentro del contexto de otro módulo. Por ejemplo, si en el módulo Vec3 se redefine +, se puede escribir Vec3.(a + b + c + d) o let open Vec3 in a + b + c + d. Al leer el código no queda la duda de “¿de dónde salió este +?”, y es mucho mejor que a.add(b).add(c).add(d). De todos modos, Zig ya se está consolidando, así que parece difícil que entren cambios grandes para resolver un problema así
    • Puede que no sepas cuánta magia corre detrás de las funciones __ de Python y PHP
      El enfoque de Zig es refrescante, y la capacidad de seguir el código es más importante que los pocos segundos que toma escribir algunos caracteres extra
    • Creo que muchas de las razones para detestar la sobrecarga de operadores tienen que ver con el rendimiento inesperado en runtime
      La solución ideal sería que el lenguaje ofreciera operadores definidos por el usuario que dejaran claro que son sobrecargas. Por ejemplo, con una forma de prefijo/sufijo como let c = a |+| b, quien lee el código puede saber que la operación |+| en realidad es una llamada a función. Aún podría abusarse, pero se reduciría una de las principales preocupaciones
    • En C++ solo usé la sobrecarga de operadores para matemática de vectores/matrices, y en ese caso fue realmente útil
      Estaría bien si el lenguaje ofreciera directamente sintaxis para matemática vectorial, como los lenguajes de shading. Zig al menos tiene el tipo @Vector(), que se parece un poco a la Vector Extension de Clang, pero lamentablemente no es la Extended Vector Extension. Ver https://ziglang.org/documentation/master/#Vector y https://clang.llvm.org/docs/LanguageExtensions.html#vectors-...
    • Quizá los operadores básicos de álgebra lineal sobre arreglos numéricos deberían venir directamente integrados en el lenguaje en vez de resolverse con sobrecarga
      No sé bien si ya existe una propuesta así
  • fieldNames se ve muy parecido a fieldPairs de Nim, y es una estructura tremendamente práctica
    Hace muy fácil crear serialización eficiente, y hace poco implementé con fieldPairs una verificación de seguridad de hilos en tiempo de compilación para tipos en unas 20 líneas. Creo que este tipo de función debería ser una capacidad estándar de los lenguajes de programación. Uno de los puntos que más extraño en Rust está justo aquí, y la última vez que lo intenté estaba limitado a macros sin tipos. Es demasiado restrictivo. No queda más que esperar que las estructuras de un crate tengan implementado serde, y tampoco se puede crear programáticamente una estructura con los mismos campos

    • En algún momento hubo una discusión sobre reflexión en tiempo de compilación, y quizá podría haber incluido algo así, pero parece que desapareció junto con el drama relacionado
      Es una pena. Cosas como serde serían mucho más fáciles de implementar si existiera reflexión en tiempo de compilación
    • Con reflexión en tiempo de compilación se pueden crear frameworks como ORM o frameworks web
      El único compromiso es que esas bibliotecas tendrían que incluirse en forma de código fuente
  • Después de haber escrito una biblioteca de parser de C más o menos completa, no estoy tan seguro de que el lenguaje en sí realmente necesite metaprogramación
    Si quieres generar estructuras, serialización, propiedades, instrumentación, etc., basta con escribir un programa normal en C que procese archivos fuente y genere archivos fuente, y ejecutarlo primero desde el script de build. Me pregunto cómo se depuran y prueban esos metaprogramas. El mío es un programa normal en C, así que usa el mismo depurador y las mismas herramientas que el resto del código

    • Eso es precisamente lo que la gente no quiere hacer
    • C#, más precisamente Roslyn/dotnet, ofrece esto bastante bien
      Como el compilador mismo está escrito en el mismo lenguaje, puedes agregar un plugin que acceda al AST en modo solo lectura y emita código fuente C#. Para depurarlo hay que trabajar un poco más para crear un buen framework de pruebas, pero después puedes ejecutar el compilador con el plugin dentro de un framework estándar de pruebas unitarias y verlo con un depurador interactivo
    • Es el mismo enfoque que defienden Ryan Fleury y otros, y es lo suficientemente bueno
      “Ejecución arbitraria en tiempo de compilación en C: cl /nologo /Zi metaprogram.c && metaprogram.exe, cl /nologo /Zi program.c. El código en tiempo de compilación corre a velocidad nativa, es depurable, completamente procedural y arbitrario. No hace falta que el compilador ejecute el código.” https://x.com/ryanjfleury/status/1875824288487571873
    • No sé mucho de Zig, pero la fuerza de Lisp está en las expresiones S, es decir, en manipular el AST
      Para hacer eso en C, tendrías que escribir un parser completo de C para el programa en C que procesa los archivos fuente
    • Hace tiempo hice algo parecido en Python con el JIT de numba
      Escribía código Python que generaba código Python, y luego hacía que se compilara de nuevo. Era un desastre frágil y espantoso, y haber tenido que hacer ese tipo de cosas fue una de las grandes razones por las que dejé Python. Es parecido a preguntar si no bastaría con pasar todos los argumentos de una función como strings. Hay gente que escribe código basado en strings, pero debería ser raro, y el lenguaje debería ofrecer una forma de evitarlo
  • Un patrón interesante es que en tiempo de compilación se pueden generar estructuras
    Hice un experimento en el que creaba un archivo JSON en PyTorch, lo leía con @embedFile de Zig y luego generaba una estructura con un método run específico para implementar una red neuronal. En teoría, esto permite que el compilador optimice directamente la red neuronal. Todavía no he demostrado una gran ventaja, pero también implica que toda la red está en el stack, sin asignación dinámica. No tengo claro si eso es bueno

    • Hice algo así escribiendo un generador de código en Python en vez de usar comptime
      No estoy seguro de que comptime de Zig sea particularmente rápido, y tampoco quiero ejecutar cada vez un parser de JSON que genera estructuras
    • Me pregunto qué impacto tiene en el tiempo de compilación
  • Es interesante, pero en casos como este tiendo a estar de acuerdo con los comentarios del tipo “D lo hizo primero”

    • Así es, y además lo hace de forma higiénica, no como un preprocesador
  • Si te sorprendió el comptime de Zig, definitivamente vale la pena mirar Nim, que tiene evaluación de código en tiempo de compilación y un sistema completo de macros sobre el AST

    • Nim es un lenguaje divertido, pero no creo que lo consideraría para trabajo “serio”
      Tiene los mismos problemas de ecosistema que otros lenguajes de nicho, más factores como un mantenedor que genera opiniones divididas, colaboradores clave que parecen no quedarse mucho tiempo y financiamiento principal de una empresa de criptomonedas. Hace 10 años esas cosas quizá no me habrían molestado, pero ahora es distinto
    • Zig tiene la ventaja de que no tiene excepciones
      Nim también parece estar intentando alejarse de las excepciones, pero las excepciones contaminan las funciones y te obligan a preocuparte por ellas aunque no uses funciones que lancen excepciones[1]. La vida es demasiado corta para lidiar con flujo de control invisible. [1]: https://github.com/status-im/nim-stew
  • Zig en general es un lenguaje bastante bueno y hace lo que necesita hacer
    Es muy importante no salirse de su propósito, y por eso no me gusta mucho que algunos lenguajes se usen en cualquier cosa solo porque se usan

  • Ojalá hubiera algo que combinara las capacidades de metaprogramación de Zig con el enorme ecosistema, la comunidad y la seguridad de Rust
    Si miro solo el diseño del lenguaje, Zig me gusta mucho más que Rust, pero como programador aficionado torpe no me siento capaz, al menos por ahora, de escribir en Zig algo realmente útil o confiable

    • Viniendo de Rust, probé Zig un rato y lo abandoné rápido
      Porque el compilador no me ayudaba a detectar esos problemas en tiempo de compilación. Sé que Zig no promete eso, pero para mí es una desventaja decisiva, así que creo que Zig no es el lenguaje adecuado para mí. En cambio, el concepto de comptime me gusta más que las macros de Rust
    • Espero que la comunidad de Rust se mantenga lejos de Zig. Es broma, pero en gran parte lo digo en serio