1 pontos por GN⁺ 2025-01-08 | 1 comentários | Compartilhar no WhatsApp
  • A metaprogramação em tempo de compilação (comptime) do Zig pode ser lida como código de runtime, mas ainda assim gerar código executável tão eficiente quanto uma função em C escrita manualmente para tarefas repetitivas, como somar campos de uma struct
  • Em vez de uma sintaxe separada para genéricos, o uso de comptime T: type e anytype para lidar com programação genérica é um dos principais casos de uso do comptime no Zig
  • Runtime, comptime e o sistema de build compartilham a mesma linguagem Zig, então até uma função como Fizz Buzz pode ser executada durante a compilação para virar um array pré-calculado
  • O compilador avalia primeiro o que for possível e deixa apenas as partes que precisam de valores de runtime como bytecode de saída que depois vira código de máquina
  • O comptime do Zig pode resolver problemas parecidos com geração de código em texto, mas sem substituir palavras-chave arbitrariamente como macros em C, o que reduz a carga de leitura e depuração do código

Uma forma de ler o comptime do Zig

  • O comptime do Zig é um recurso de metaprogramação, mas ao ler o comportamento final não é preciso se preocupar demais com a distinção entre tempo de compilação e runtime
  • O terceiro item do zig zen, “Favor reading code over writing code”, é um critério importante
    • Metaprogramação baseada em macros ou geração de código pode criar duas camadas: o código original e o código expandido
    • Esse tipo de camada indireta dificulta a leitura do código, a depuração e a mudança de comportamento
  • O texto compara lado a lado o código de runtime que soma o array [3]i64 = .{1,2,3} e o código de comptime que soma os campos a, b, c da struct MyStruct
    • inline for (comptime std.meta.fieldNames(MyStruct)) percorre os nomes dos campos
    • @field(my_struct, field_name) acessa o campo usando um nome conhecido em tempo de compilação
  • O exemplo de soma dos campos da struct parece reflexão em runtime, mas no executável fica código tão eficiente quanto uma função de soma escrita manualmente para aquele tipo de struct
  • O processo de transformação mostrado no exemplo não reproduz exatamente a implementação do Zig, sendo mais uma explicação conceitual
  • A equipe principal do Zig está trabalhando em um depurador capaz de executar passo a passo código misturando comptime e runtime como no exemplo

Genéricos são tratados com comptime

  • O Zig não tem um recurso separado de genéricos
  • Para tornar um tipo genérico, escreve-se uma função que recebe um tipo e retorna um tipo
    • Ex.: pub fn GenericMyStruct(comptime T: type) type
    • Os campos a, b, c da struct retornada usam todos o tipo T
  • Funções genéricas também podem ser escritas da mesma forma
    • Ex.: fn quadratic(comptime T: type, a: T, b: T, c: T, x: T) T
    • É possível explicitar o argumento de tipo como em quadratic(f32, ...) e quadratic(i64, ...)
  • Se o tipo do argumento não for importante para outras partes da assinatura da função, ele pode ser inferido com o tipo especial anytype
  • Só isso não explica todo o comptime, mas já serve como ponto de partida para entender trabalhos genéricos comuns no Zig

Executando o mesmo código Zig em tempo de compilação

  • O Zig usa a mesma linguagem em runtime, comptime e no sistema de build
  • No exemplo de Fizz Buzz, a função fizzBuzz(writer) é escrita primeiro como uma função comum de saída em runtime
    • Ela percorre de 1 até 100
    • Para múltiplos de 3 e 5, imprime fizzbuzz; para múltiplos de 3, fizz; para múltiplos de 5, buzz; caso contrário, imprime o número
  • Ao executar a mesma função fizzBuzz dentro de um bloco comptime, toda a saída pode ser pré-calculada
    • std.io.countingWriter(std.io.null_writer) calcula primeiro a quantidade de bytes necessária
    • Com o tamanho calculado, cria-se o array buffer: [cw.bytes_written]u8
    • A saída é escrita de novo em std.io.fixedBufferStream(&buffer) para produzir o valor full_fizzbuzz
  • Medindo apenas o trecho principal, a versão pré-calculada roda cerca de 9 vezes mais rápido
    • Como o exemplo é pequeno, o tempo total de execução ainda depende mais de outros fatores
  • Existem diferenças entre comptime e runtime quanto aos valores e recursos acessíveis
    • Valores comptime_int, comptime_float e type só podem ser acessados em comptime
    • Algumas funções aceitam apenas argumentos de comptime e, na prática, ficam restritas ao comptime
    • Chamadas de sistema e recursos que dependem delas só podem ser acessados em runtime
  • Código que não usa esses recursos exclusivos funciona da mesma forma em comptime e runtime

Entendendo comptime como avaliação parcial

  • O comptime pode ser visto como avaliação parcial (partial evaluation) durante o processo de compilação
  • Avaliação parcial é a técnica de, com apenas alguns argumentos de uma função disponíveis, substituir e calcular primeiro as expressões que usam valores já conhecidos, criando depois uma nova função que recebe só os argumentos ainda desconhecidos
  • O exemplo de soma do array mostra esse processo passo a passo
    • O loop for é desdobrado em instruções separadas para cada iteração
    • array[0], array[1], array[2] são substituídos por 1, 2, 3
    • sum é atualizado de 0 → 1 → 3 → 6
    • No fim, tudo é simplificado até uma forma como std.debug.print(..., .{6})
  • O exemplo de soma dos campos da struct pode ser lido do mesmo jeito
    • inline for é executado em comptime, então é expandido em instruções separadas para os campos "a", "b", "c"
    • @field(my_struct, "a") vira my_struct.a
    • A função final passa a conter diretamente sum += my_struct.a, sum += my_struct.b, sum += my_struct.c
  • Essa abordagem permite expressar a intenção diretamente no código e ainda reduz a necessidade de deixar comentários pedindo atualização manual da função de soma quando os campos mudarem

Avaliação em tempo de compilação e emissão de código de runtime

  • O comptime do Zig também pode ser visto como a combinação de avaliação em tempo de compilação e emissão de código para runtime
  • O código de entrada é executado pelo compilador
    • Instruções que podem ser conhecidas em tempo de compilação são avaliadas imediatamente
    • Instruções que exigem valores de runtime são adicionadas ao código de saída
  • Essa visão é a mais próxima da forma real como o compilador do Zig trabalha
    • Primeiro o Zig faz o parse da sintaxe
    • Depois transforma o código em bytecode para uma máquina virtual
    • A VM avalia as partes possíveis e emite novo bytecode para as partes que precisam ser tratadas em runtime
    • Esse novo bytecode é convertido depois em código de máquina
  • Condicionais como if que dependem de entrada de runtime fazem com que os dois caminhos sejam emitidos
  • Código morto não passa por análise semântica
    • Mesmo que uma função esteja errada, talvez não apareça erro de compilação até ela ser realmente usada
    • Essa característica torna a compilação mais eficiente e permite compilação condicional natural sem #ifdef
  • No Zig, nomes de tipos também são expressões avaliadas em comptime com valor type
    • Mesmo nomes de tipo que parecem simples, como tipos de argumentos de função, são resultados de avaliação em comptime
    • Essa característica torna possíveis os exemplos de genéricos mostrados antes
    • Se necessário, tipos podem ser calculados com expressões mais complexas
  • A análise estática do Zig é mais complexa do que em muitas outras linguagens de tipagem estática
    • Para descobrir todos os tipos, o compilador precisa executar uma parte considerável de si mesmo
    • Até as ferramentas acompanharem isso, recursos de editor como autocomplete nem sempre funcionam tão bem

Relação com geração de código em texto

  • O comptime do Zig tem uma forma diferente da geração de código em texto, mas pode resolver problemas parecidos
  • A função de geração writeSumFn usa writer.print para emitir o código-fonte da função sumFields
    • O código executado no gerador corresponde à parte de comptime no Zig
    • O código emitido pelo gerador corresponde ao código de runtime
  • A versão com comptime no Zig expressa a mesma lógica de forma mais direta
    • std.meta.fieldNames(MyStruct) obtém os nomes dos campos
    • inline for e @field constroem o acesso aos campos
  • Geração de código que usa informação de tipo como entrada fica mais simples no Zig
    • Na abordagem de geração textual, é preciso resolver separadamente de onde vêm o nome do tipo e os nomes dos campos
    • No Zig, o próprio tipo e as informações dos campos podem ser manipulados diretamente em comptime
  • Quando a entrada vem de algo como uma especificação externa, é possível incluir o conteúdo do arquivo com @embedFile e depois fazer o parse normalmente
  • No exemplo da struct genérica, o comptime trata referências a nomes de tipos de forma mais direta do que a geração de código em texto
    • Na geração textual, seria preciso concatenar strings como MyStruct_{s} e manter tudo consistente
    • Com comptime, basta usar a função diretamente como em GenericMyStruct(T)
  • Também há exceções
    • É possível criar tipos cujos nomes de campos são decididos em comptime
    • Nesse caso, é preciso chamar uma função embutida com uma especificação que contenha a lista de definições dos campos
    • Esses tipos não podem definir declarações como métodos
    • Isso não limita a expressividade do código em si, mas limita a forma da API que pode ser exposta a outros códigos
  • A maior parte do que faz sentido fazer com macros textuais em C também pode ser feita com comptime, embora a forma do código possa ser bem diferente
  • O Zig não oferece um recurso que substitua palavras-chave por outros nomes como fazem macros textuais

Leituras adicionais e exemplos da biblioteca padrão

  • O Zig em si não é uma linguagem que depende apenas de comptime, e é possível aprender mais no site oficial
  • A referência da linguagem trata dos recursos específicos de comptime
  • A biblioteca padrão do Zig tem exemplos de uso de comptime
    • A função de formatação usada por std.debug.print é uma função genérica poderosa
      • Em muitas linguagens, a string de formatação é interpretada em runtime, e validadores separados podem ser adicionados para detectar erros mais cedo
      • No Zig, a string de formatação é analisada em comptime para gerar código de saída eficiente, e toda a validação também é feita em tempo de compilação
    • ArrayList é um contêiner genérico relativamente simples, mas completo em recursos
    • A função main do Zig pode ter vários tipos de retorno, e isso não é mágica do compilador, mas sim tratado com código comum de comptime

1 comentários

 
GN⁺ 2025-01-08
Opiniões no Hacker News
  • Em vez de elogiar sem crítica problemas já conhecidos em programação em tempo de compilação, seria bom que o texto se aprofundasse mais
    Programação em estágios não é novidade, e famílias de linguagens Lisp fazem coisas semelhantes há décadas, mas nesse processo muitos trade-offs e problemas de projeto vieram à tona. Por exemplo, genéricos desse tipo quebram a parametricidade (parametricity), tornando difícil inferir algo apenas pela assinatura de tipo de uma função. Também não está claro como Zig lida com tipos genéricos recursivos, e a ordem e o momento em que verificação de tipos e computação em tempo de compilação interagem também são escolhas importantes. O texto diz que código em tempo de compilação pode gerar não só valores, mas também código, porém não trata de higiene (hygiene). Uma boa discussão relacionada é https://typesanitizer.com/blog/zig-generics.html

    • Gosto bastante de Zig e venho acompanhando e usando ocasionalmente há alguns anos, mas comptime brilha especialmente em casos como genéricos, inicialização em tempo de compilação de estruturas de dados complexas e geração de código específica por alvo
      Dito isso, em outras situações, quando vejo comptime em código Zig, às vezes penso “ah…”. Como macros de Lisp, é fácil usá-lo para contornar problemas que, na verdade, não existiriam ou desapareceriam com uma estrutura de código melhor. O exemplo do artigo original, que percorre campos de uma struct para somar valores, é típico de como as pessoas usam comptime na prática, mas em muitos casos é melhor usar uma estrutura de dados que seja realmente iterável, como std.enums.EnumArray
    • Fico curioso se a afirmação de que a parametricidade é quebrada se refere ao nível em que um programador prático entende o comportamento de uma função, ou ao nível de inferência dentro de um sistema de teoria dos tipos, como cálculo lambda tipado
      Em teoria, a preocupação é clara, mas o quanto isso importa em uma linguagem prática é outra questão. Templates de C++ também quebram a parametricidade por meio de especialização de templates, mas na prática isso tende a não ser um grande problema e, pelo contrário, permite otimizações. Por exemplo, uma implementação como std::vector<bool>, que armazena cada elemento efetivamente em 1 bit, só é possível com esse tipo de flexibilidade
    • Como autor, escrevi este texto para organizar o que tentei explicar em um encontro e não consegui comunicar bem, então foquei mais em explicação do que em crítica
      Vejo o comptime ao estilo Zig como um trade-off direto entre inferibilidade e expressividade em comparação com assinaturas de função definidas de forma mais estática. Ele afeta apenas código que faz esse tipo de inferência por tipos, e é uma parte que pode ser escolhida conforme a necessidade. Mesmo quando a assinatura de tipo não foi suficiente, ler algumas dezenas de linhas de fonte geralmente resolveu como usar, e nunca fiquei confuso por muito tempo. No exemplo de tipo genérico recursivo, usar o nome que está sendo declarado gera “dependency loop detected”, mas há formas de contornar isso, e o exemplo genérico do texto também referencia a si mesmo. A verificação de tipos acontece durante comptime; por exemplo, o primeiro @compileLog("Hi") é executado, mas se depois houver um erro de tipo em const a: u32 = "42";, o segundo log não é alcançado. Podem surgir problemas sutis na verificação de tipos entre comptime e runtime, mas eles aparecem só em códigos bastante difíceis, são fáceis de resolver, e a equipe principal também sabe disso, então espero que sejam tratados antes da 1.0. Seria preciso explicar melhor o que exatamente se quer dizer por higiene
    • A ideia de que é possível inferir algo só olhando a declaração da função é separada de execução em tempo de compilação
      Se a função tem um propósito lógico claro e bons nomes para ela e seus parâmetros, dá para inferir bastante apenas pela declaração. Partes que o programador pode especificar, como rótulos de parâmetros e nomes de tipos, também podem ser vistas como parte do nome. Concordo com a conclusão do título do texto linkado, mas ele parece mais um texto de equipe de debate tentando marcar pontos do que uma argumentação significativa. Um enquadramento melhor é flexibilidade versus complexidade. Um sistema genérico fixo, se bem projetado, é mais simples do que um programável, mas menos flexível. A vantagem de Zig é que a linguagem de metaprogramação é quase igual à linguagem comum, e a própria linguagem comum também é simples, reduzindo o custo de complexidade adicional. Ainda assim, como código em tempo de compilação e código em runtime se misturam e há poucos indícios, surge a complexidade de dificultar para o programador distinguir o que é executado quando. Fico pensando se um language server ou plugin de editor poderia oferecer um “shader de comptime” que desse uma cor de fundo diferente ao código comptime
    • É lamentável que este tema esteja sendo discutido com base em uma demo de 15 minutos e com pouca compreensão dos trade-offs reais. O texto linkado de Varun Gandhi é excelente
      Pela experiência com Rust, muitas coisas que as pessoas tentam fazer com genéricos constantes parecem que seriam mais fáceis com um recurso como comptime. Permitir aritmética sobre genéricos constantes preservando a parametricidade é difícil de implementar e, se o que se quer de fato é algo como “uma trait de função hash cujo tamanho de saída é N”, pode ser aceitável abrir mão da parametricidade para esse objetivo e transformar N em uma trait em uma etapa anterior de geração de código. Mas macros de Rust são flexíveis e trabalhosas demais para serem usadas desse jeito. Ainda assim, no momento em que se substitui polimorfismo paramétrico por um recurso ingênuo de geração de código, começa o caminho difícil
  • D já tinha isso há 17 anos. Recursos de D vêm sendo constantemente levados para outras linguagens
    Zig marca, com a palavra-chave comptime, os blocos que serão executados durante a compilação, mas em D quem decide a execução não é uma palavra-chave, e sim se é uma expressão constante. Por exemplo, int s = sum(3, 4); é executado em tempo de execução, e enum e = sum(3, 4); é executado em tempo de compilação. Se chamadas a funções do sistema como globais não constantes, entrada/saída e malloc() forem evitadas, um bom número de funções pode ser executado em tempo de compilação sem alterações. Graças ao gerenciamento automático de memória de D, alocação de memória também é possível

    • Um dos usos de que gosto é a geração de tabelas estáticas
      Antes era preciso escrever um programa separado para criar tabelas estáticas, mas com execução de funções em tempo de compilação isso deixou de ser necessário. Os valores de inicialização do array tytab vêm de uma lambda que calcula e retorna um array. O exemplo completo está em https://github.com/dlang/dmd/blob/master/compiler/src/dmd/ba.... Outro uso comum de CTFE é criar DSLs
    • Não quero perder a oportunidade de agradecer por ter criado D. Mas fico curioso sobre os limites da execução em tempo de compilação
      Se a máquina em que o compilador roda e a máquina em que o programa será executado forem diferentes, como o compilador D garante a correção? Por exemplo, fico pensando como o compilador sabe que int s = sum(100000, 1000000) terá o mesmo valor em todas as máquinas x86. Pode haver diferenças sutis entre gerações de CPU, então como se garante que um cálculo feito de fato no host terá o mesmo resultado na máquina-alvo? Ou presume-se que, se a arquitetura for a mesma, host e alvo são suficientemente parecidos?
    • O ImportC de D também permite fazer CTFE com código C
      Dá para escrever int sum(int a, int b) { return a + b; } e usar algo como _Static_assert(sum(3, 4) == 7, "look ma, check at compile time!");. Não sei por que o padrão C não adiciona isso, e funciona bem
    • Para ser justo, Zig também permite chamar a mesma função tanto em contexto de tempo de execução quanto em contexto comptime
      square(2) é uma chamada em tempo de execução, e comptime square(3) é uma chamada em tempo de compilação. Uma chamada comptime gera erro de compilação se algo não for compatível com tempo de compilação, e vejo isso como um recurso importante. Isso porque serve como sinal de alerta quando um código que se esperava executar em tempo de compilação acaba, por acaso, sendo empurrado para avaliação em tempo de execução por causa de mudanças nos argumentos de entrada
  • Zig parece interessante, mas eu gostaria que tivesse sobrecarga de operadores
    A maioria dos argumentos contra sobrecarga de operadores não me convence. A alegação de que não se sabe o que realmente está acontecendo por baixo não é suficiente, já que também é possível criar uma função chamada add que faça multiplicação. iostreams ou boost::spirit em C++ aparecem com frequência como exemplos de abuso, mas em outras linguagens com sobrecarga de operadores isso não parece acontecer muito, e parece mais um problema específico de C++

    • Uma solução no estilo OCaml parece combinar bem com os usos de Zig
      Em OCaml, é possível redefinir operadores, mas apenas dentro do contexto de outro módulo. Por exemplo, se o módulo Vec3 redefiniu +, dá para usar Vec3.(a + b + c + d) ou let open Vec3 in a + b + c + d. Ao ler o código, não surge a dúvida “de onde veio este +?”, e isso é muito melhor do que a.add(b).add(c).add(d). Mas Zig já está se consolidando, então parece difícil que entre uma mudança grande para resolver esse tipo de problema
    • Pode ser difícil saber quanta mágica acontece por trás das funções __ de Python e PHP
      A abordagem de Zig é revigorante, e a capacidade de acompanhar o código é mais importante do que os poucos segundos gastos digitando alguns caracteres a mais
    • Acho que boa parte dos motivos para não gostar de sobrecarga de operadores tem a ver com desempenho em tempo de execução inesperado
      A solução ideal seria a linguagem oferecer operadores definidos pelo usuário que deixem claro que se trata de uma sobrecarga. Por exemplo, com uma forma de prefixo/sufixo como let c = a |+| b, quem lê o código sabe que a operação |+| na prática é uma chamada de função. Ainda poderia ser abusado, mas uma das principais preocupações seria reduzida
    • Em C++, usei sobrecarga de operadores apenas para matemática de vetores/matrizes, e nesse caso foi realmente útil
      Se a linguagem oferecesse diretamente uma sintaxe de matemática vetorial, como linguagens de shading, acho que seria bom. Zig tem ao menos o tipo @Vector(), um pouco parecido com a Vector Extension do Clang, mas infelizmente não é a Extended Vector Extension. Veja https://ziglang.org/documentation/master/#Vector e https://clang.llvm.org/docs/LanguageExtensions.html#vectors-...
    • Talvez operadores básicos de álgebra linear para arrays numéricos devam simplesmente ser embutidos na linguagem, em vez de depender de sobrecarga
      Não sei bem se já existe alguma proposta assim
  • fieldNames parece muito parecido com o fieldPairs de Nim, e é uma estrutura extremamente conveniente
    Torna muito fácil criar serialização eficiente, e recentemente implementei, em cerca de 20 linhas, uma verificação em tempo de compilação de segurança de threads para tipos usando fieldPairs. Acho que recursos assim deveriam se tornar padrão em linguagens de programação. Uma das coisas de que mais sinto falta em Rust está aqui; da última vez que tentei, estava limitado a macros sem tipo. É restritivo demais. Só resta torcer para que uma struct de um crate tenha serde implementado, e também não dá para criar programaticamente uma struct com os mesmos campos

    • Houve, em algum momento, uma discussão sobre reflexão em tempo de compilação, e talvez isso pudesse incluir esse tipo de recurso, mas parece ter desaparecido junto com o drama relacionado
      É uma pena. Coisas como serde seriam muito mais fáceis de implementar com reflexão em tempo de compilação
    • Com reflexão em tempo de compilação, seria possível criar frameworks como ORMs ou frameworks web
      O único compromisso é que essas bibliotecas teriam de ser incluídas em forma de código-fonte
  • Depois de escrever uma biblioteca de parser de C relativamente completa, não sei se concordo com a ideia de que a própria linguagem precisa de metaprogramação
    Se você quer gerar structs, serialização, propriedades, instrumentação etc., basta escrever um programa C comum que processe arquivos-fonte e produza arquivos-fonte, e executá-lo antes no script de build. Fico curioso para saber como esses metaprogramas são depurados e testados. O meu é um programa C comum, então uso o mesmo depurador e as mesmas ferramentas que uso para o restante do código

    • É exatamente esse tipo de coisa que as pessoas não querem fazer
    • C#, mais precisamente Roslyn/dotnet, oferece isso bastante bem
      Como o próprio compilador é escrito na mesma linguagem, dá para inserir um plugin que tenha acesso somente leitura à AST e emita código-fonte C#. Para depuração, é preciso um pouco mais de trabalho para criar um bom framework de testes, mas depois disso dá para executar o compilador com o plugin dentro do framework padrão de testes unitários e inspecionar tudo com um depurador interativo
    • É a mesma abordagem defendida por Ryan Fleury e outros, e é uma forma suficientemente boa
      “Execução arbitrária em tempo de compilação em C: cl /nologo /Zi metaprogram.c && metaprogram.exe, cl /nologo /Zi program.c. O código em tempo de compilação roda em velocidade nativa, é depurável, totalmente procedural e arbitrário. O compilador não precisa executar o código para você.” https://x.com/ryanjfleury/status/1875824288487571873
    • Não conheço bem Zig, mas o poder de Lisp está nas expressões S, ou seja, em manipular a AST
      Para fazer isso em C, seria preciso escrever um parser completo de C para o programa C que processa os arquivos-fonte
    • Já fiz algo parecido em Python com o JIT do numba
      Escrevia código Python que gerava código Python e mandava compilar de novo. Era uma bagunça frágil e horrível, e ter de fazer esse tipo de coisa foi uma das grandes razões para eu sair de Python. É parecido com perguntar por que não passar todos os argumentos de função como strings. Há quem use código baseado em strings, mas isso deveria ser raro, e a linguagem deveria oferecer meios de evitar isso
  • Um padrão interessante é poder gerar structs em tempo de compilação
    Fiz um experimento em que eu criava um arquivo JSON no PyTorch, lia com @embedFile no Zig e gerava structs com um método run específico para implementar uma rede neural. Em teoria, isso permite que o compilador otimize diretamente a rede neural. Ainda não consegui demonstrar uma grande vantagem, mas também há o fato de a rede inteira ficar na stack, sem alocação dinâmica. Não tenho certeza se isso é bom

    • Já fiz esse tipo de coisa escrevendo um gerador de código em Python em vez de usar comptime
      Não tenho certeza de que comptime em Zig seria especialmente rápido, e também não quero rodar toda vez um parser de JSON que gera structs
    • Fico curioso sobre o impacto disso no tempo de compilação
  • É interessante, mas, em casos assim, acabo concordando com comentários do tipo “D fez isso primeiro”

    • Sim, e de forma higiênica, sem ser uma abordagem de pré-processador
  • Se o comptime do Zig impressionou você, vale muito a pena olhar também Nim, que tem avaliação de código em tempo de compilação e um sistema completo de macros sobre a AST

    • Nim é uma linguagem divertida, mas acho que eu não a consideraria para trabalho “sério”
      Ela tem os mesmos problemas de ecossistema de outras linguagens de nicho, além de fatores como um mantenedor que divide opiniões, contribuidores centrais que parecem não permanecer por muito tempo e financiamento principal vindo de uma empresa de criptomoedas. Dez anos atrás essas coisas talvez não me incomodassem, mas hoje é diferente
    • Zig tem a vantagem de não ter exceções
      Nim também parece estar tentando se afastar de exceções, mas exceções contaminam funções e obrigam você a se preocupar com elas mesmo quando não usa funções que lançam exceções[1]. A vida é curta demais para lidar com fluxo de controle invisível. [1]: https://github.com/status-im/nim-stew
  • Zig, no geral, é uma linguagem bem boa e faz o que precisa fazer
    É muito importante não se desviar do propósito, e por isso não gosto muito quando algumas linguagens são usadas em qualquer lugar só porque simplesmente são usadas

  • Eu gostaria que existisse algo que combinasse as capacidades de metaprogramação do Zig com o enorme ecossistema, a comunidade e a segurança do Rust
    Olhando apenas para o design da linguagem, gosto muito mais de Zig do que de Rust, mas, como programador amador pouco habilidoso, pelo menos por enquanto não tenho confiança para escrever em Zig algo realmente útil ou confiável

    • Como alguém acostumado com Rust, tentei Zig por pouco tempo e desisti rapidamente
      Porque o compilador não me ajudava encontrando esse tipo de problema em tempo de compilação. Sei que Zig não promete isso, mas para mim é uma desvantagem decisiva, então acho que Zig não é a linguagem certa para mim. Por outro lado, gosto mais do conceito de comptime do que das macros de Rust
    • Eu gostaria que a comunidade Rust ficasse bem longe do Zig. É piada, mas em grande parte é sério