Zig tem muitos recursos para programação de baixo nível, notavelmente como: packed struct (estrutura de variáveis struct com preenchimento de zero entre os campos), inteiros de largura arbitrária[12] e vários tipos de ponteiro.[13]
O compilador é escrito em Zig, atualmente utiliza LLVM[14] como back-end[15][16] principal além de possuir um back-end próprio ainda em desenvolvimento, suportando muitos de seus alvos nativos.[17] O compilador é um software livre e de código aberto sob a licença MIT.[18] O compilador Zig expõe a capacidade de compilar C e C++, semelhante ao Clang, usando os comandos zig cc e zig c++, respectivamente.[19] A linguagem de programação Nim suporta o uso de zig cc como um compilador C.[20]
O desenvolvimento Zig é financiado pela Zig Software Foundation (ZSF), uma corporação sem fins lucrativos comandado por Andrew Kelley como presidente, que recebe doações e contrata vários empregados a tempo inteiro.
Filosofia
Sem estruturas de controle oculta
Se o código Zig não parece estar pulando para chamar uma função, então não está. Isso significa que você pode ter certeza de que o código a seguir chama apenas foo()e então bar(), e isso é garantido sem a necessidade de saber os tipos de nada:
var a = b + c.d;
foo ();
bar ();
D tem as funções @property, que são métodos que você chama com o que parece ser acesso de campo, portanto, no exemplo acima, c.d pode chamar uma função. Rust tem sobrecarga do operador, então o operador + pode chamar uma função. D tem exceções throw/catch, portanto, foo() pode lançar uma exceção e evitar que bar() seja chamado. (Claro, até mesmo em Zigfoo() poderia travar e impedir bar() de ser chamado, mas isso pode acontecer em qualquer linguagem que seja Turing-Completude). O objetivo desta decisão de design é melhorar a legibilidade.
Sem Alocações ocultas
Zig tem uma abordagem de trabalho manual quando se trata de alocação de memória. Não há uma palavra-chave new ou qualquer outro recurso semelhante na linguagem que utilize um alocador de memória (por exemplo, operador de concatenação de strings). Todo o conceito da alocação de memória é gerenciado pela biblioteca e o código da aplicação, não pela linguagem.
Exemplo de alocações ocultas:
Go utiliza defer para alocar memória a uma pilha de funções locais. Além de ser uma forma não intuitiva para este fluxo de controle funcionar, ele pode causar falhas como falta de memória se você usar defer dentro de um laço de repetição.
Em Go, uma chamada de função pode causar alocação de memória porque as corrotina em Go (goroutine) alocam pequenas pilhas que são redimensionadas quando a pilha de chamadas fica muito cheia.
As principais APIs da biblioteca padrão Rust entram em pânico em condições de falta de memória, e as APIs alternativas que aceitam parâmetros de alocação serão um problema a ser considerado mais tarde
Quase todas as linguagens que possuem coletor de lixo têm alocações ocultas, já que o coletor de lixo esconde as evidências no lado da liberação de memória.
O principal problema com alocações ocultas é que elas impedem a reusabilidade de um pedaço do código, limitando desnecessariamente o número de ambientes aos quais o código seria apropriado para ser implantado. Simplificando, existem casos de uso em que se deve ser capaz de confiar no fluxo de controle e chamadas de função para não ter o efeito colateral da alocação de memória, portanto, uma linguagem de programação só pode servir a estes casos de uso se ela puder fornecer esta garantia de forma realista.
Em Zig, há recursos da biblioteca padrão que fornecem e trabalham com alocadores de memória, mas esses são recursos opcionais da biblioteca padrão, já que não são incorporados a linguagem. Se você nunca inicializar um alocador de memória, você pode ter certeza de que seu programa não irá alocar memória.
Cada recurso da biblioteca padrão que precisa alocar memória aceita um parâmetro Allocator para fazer isso. Isto significa que a biblioteca padrão do Zig suporta dispositivos freestanding. Por exemplo std.ArrayList e std.AutoHashMap podem ser utilizadas para programação em bare-metal!
Alocadores personalizados fazem gerenciamento manual de memória com facilidade. Zig tem um alocador de depuração que mantém a segurança da memória em casos de ponteiro selvagem e liberação dupla. Ela automaticamente detecta e imprime vestígios de vazamentos de memória na pilha. Há um alocador de arena para que você possa agrupar qualquer número de alocações em uma e liberá-las todas de uma só vez, em vez de gerenciar cada alocação independentemente. Alocadores para fins especiais podem ser usados para melhorar o desempenho ou uso de memória para qualquer necessidade específica de aplicação.
Autonomia
Zig tem uma biblioteca padrão inteiramente opcional que só é compilada em seu programa se você a utilizar. O mesmo ocorre com a biblioteca C, a utilização dela é opcional, exceto em casos de interoperabilidade com linguagem C. O Zig é amigável ao desenvolvimento de sistemas embarcados e bare-metal.
Uma linguagem portátil para bibliotecas
Um dos santo graal da programação é a reutilização de código. Infelizmente, na prática, acabamos reinventando a roda várias vezes. Muitas vezes é justificado.
Se um aplicativo tem requisitos de tempo real, qualquer biblioteca que usa coleta de lixo ou qualquer outro comportamento não determinístico é desqualificada como dependência.
Se uma linguagem torna muito fácil ignorar erros e, portanto, verificar se uma biblioteca trata corretamente e gera bolhas de erros, pode ser tentador ignorar a biblioteca e implementá-la novamente, sabendo que todos os erros relevantes foram tratados corretamente. O Zig é projetado de forma que a coisa mais preguiçosa que um programador pode fazer é lidar com os erros corretamente e, portanto, pode-se estar razoavelmente confiante de que uma biblioteca irá propagar erros.
Atualmente, é pragmaticamente verdade que C é a linguagem mais versátil e portátil. Qualquer linguagem que não tenha a capacidade de interagir com o código C corre o risco de ser obscurecida. O Zig está tentando se tornar a nova linguagem portátil para bibliotecas, tornando-o simultaneamente direto para a conformidade com a CABI para funções externas e introduzindo segurança e design de linguagem que evita bugs comuns nas implementações.
Simplicidade
C++, Rust e D têm um grande número de recursos e podem desviar a atenção do significado real do aplicativo em que você está trabalhando. Alguém se encontra depurando seu conhecimento da linguagem de programação em vez de depurar o próprio aplicativo.
O Zig não possui macros nem metaprogramação, mas ainda assim é poderoso o suficiente para expressar programas complexos de uma forma clara e não repetitiva. Inclusive o Rust que possui casos especiais de macros format!, implementando-o no próprio compilador. Enquanto isso, no Zig, a função equivalente é implementada na biblioteca padrão sem nenhum código de caso especial no compilador.
Quando você olha para o código Zig, tudo é uma expressão simples ou uma chamada de função. Não há sobrecarga de operador, métodos de propriedade, despacho de tempo de execução, macros ou fluxo de controle oculto. Zig busca toda a bela simplicidade de C, exceto as armadilhas.
Etapas do Compilador
Recentemente houve mudanças no bootstrap no qual é utilizado uma versão anterior do zig no formato Wasm.[21]
Antes
O processo de desenvolvimento do compilador se encontra em 4 etapas, embora apenas 3 sejam aderidos atualmente.
Durante essa mudança o compilador deixou de possuir mais de 80,000 linhas de código escritos em C++ e também torna o parâmetro -fstage1 e -fno-stage1 (Etapa 1) obsoleto na versão 0.11 ou posterior, restringindo-se a utilização do compilador auto-hospedado (Etapa 2).
Representação Visual
As demais etapas posteriores ao 1 (um), apresentam instabilidade com a biblioteca padrão, embora suportem mais plataformas e sistemas, quanto ao 0 (zero) serve apenas como base inicial. O objetivo é tornar-se totalmente independente do LLVM, aderindo a Etapa 2 (dois) como padrão, previsto na versão 1.0.
Software desenvolvido em Zig
Banco de Dados
Tigerbeetle – É um banco de dados distribuído focado em transações financeiras.[22]
Blockchain
Sig – É uma implementação do validador Solana otimizada de forma inteligente e escrita em Zig.[23]
Motor Gráfico
Mach – Motor gráfico para desenvolvimento de jogos.[24]
Sistema de construção
Hermetic_cc_toolchain – Cadeia de ferramentas Bazel C/C++ para compilação cruzada de programas C/C++.[25]
Terminal
Ghostty – Emulador de terminal rápido, rico em recursos e multiplataforma que usa UI nativa da plataforma e aceleração de GPU.[26]
conststd=@import("std");constdebug=std.debug;constheap=std.heap;constmem=std.mem;// '!' esta sintaxe antes do tipo void indica ao compilador Zig que a função retornará um erro ou um valor.pubfnmain()!void{constola="Olá,";// Qualquer variavel const ou valor literal é de tempo de compilaçãodebug.print("\n{}{}\n",.{ola," mundo!"});// Método 1: concatenação de arranjo (array)// Só funciona se os valores forem de tempo de compilação.constola_mundo_at_comptime=ola++" mundo!";debug.print("{}\n",.{ola_mundo_at_comptime});// Método 2: std.mem.concat - Biblioteca padrãovarbuf:[128]u8=undefined;// Alocando um tamanho fixo de memória, equivalente ao tamanho do bufconstallocator=&heap.FixedBufferAllocator.init(&buf).allocator;constola_mundo_concatenated=trymem.concat(allocator,u8,&[_][]constu8{ola," mundo!"});debug.print("{}\n",.{ola_mundo_concatenated});// Método 3: std.mem.join - Biblioteca padrãoconstola_mundo_joined=trymem.join(allocator," ",&[_][]constu8{ola,"mundo!"});debug.print("{}\n",.{ola_mundo_joined});}
conststd=@import("std").c;constc=@cImport({// Veja em: https://github.com/ziglang/zig/issues/515// @cDefine("_NO_CRT_STDIO_INLINE", "1");@cInclude("stdio.h");});pubfnmain()void{_=std.printf("Método 1: Código C em Zig!\n");// Requer libc (nativo do sistema)_=c.printf("Método 2: Código C em Zig!\n");// Requer libc (nativo do sistema)}// Compilação: zig build-exe ffi.zig -lc
Fibonacci
constexpect=@import("std").testing.expect;fnfibonacci(index:u32)u32{if(index<2)returnindex;returnfibonacci(index-1)+fibonacci(index-2);}test"fibonacci"{// testando a função de fibonacci em tempo de execuçãoexpect(fibonacci(7)==13);// testando a função de fibonacci em tempo de compilaçãocomptime{expect(fibonacci(7)==13);}}// Saída:// $ zig test test.zig// 1/1 test "fibonacci"... OK// All 1 tests passed.