6 pontos por GN⁺ 2025-09-27 | 3 comentários | Compartilhar no WhatsApp
  • Em gerenciamento de memória, o Zig oferece uma abordagem mais simples e intuitiva do que o Rust
  • O borrow checker do Rust é poderoso, mas no desenvolvimento de pequenas ferramentas CLI ele acaba gerando complexidade excessiva e sobrecarga para o desenvolvedor
  • O gerenciamento manual de memória no Zig permite garantir segurança de memória eficiente apenas com as ferramentas certas e um pouco de disciplina por parte do desenvolvedor
  • A segurança de um programa depende não só da segurança de memória, mas também de vários fatores como comportamento previsível, desempenho administrável e proteção de dados
  • Rust é adequado para sistemas de grande porte, mas para pequenas ferramentas CLI práticas, o Zig leva vantagem em produtividade e manutenção

Visão geral

Ultimamente, ao criar ferramentas CLI, tenho priorizado o Zig em vez de Rust

Fundamentos do gerenciamento de memória: stack e heap

  • A stack é uma região de memória rápida e de tamanho fixo que armazena dados muito temporários, como parâmetros de função, variáveis locais e endereços de retorno
  • A heap é a área usada para alocação dinâmica de memória, quando os dados vivem por mais tempo ou quando seu tamanho só é definido em tempo de execução
  • A stack é estruturalmente simples, mas tem espaço limitado; já a heap exige atenção em termos de velocidade e fragmentação

O Borrow Checker do Rust

  • O borrow checker do Rust garante segurança de memória em tempo de compilação
  • Ele impõe regras sobre referências, ownership e lifetime, prevenindo antecipadamente erros como dereferência de ponteiro nulo e dangling pointers
  • No entanto, a segurança de memória é verificada apenas com base no tempo de compilação, e isso não elimina erros do usuário nem problemas complexos de modelagem de ownership

Exemplo: minha própria CLI de Notes

  • Ao tentar escrever em Rust uma CLI para gerenciar notas pessoais, precisei redesenhar toda a estrutura por causa do borrow checker
  • Já no Zig, apenas com allocators, foi muito mais simples criar índices baseados em ponteiros e fazer alterações ou remoções livremente
  • O borrow checker do Rust tem um objetivo claro, mas o Zig permite atingir alto nível de eficiência e segurança apenas com conhecimento básico de gerenciamento de memória e disciplina

Quando segurança de memória não é tudo para a segurança de uma ferramenta CLI

  • A verdadeira segurança de um produto inclui muitos fatores, como comportamento previsível, feedback significativo em caso de erro, proteção de dados sensíveis e resistência a ataques
  • Nem Rust nem Zig podem ser considerados realmente “seguros” se não atenderem também a requisitos além da segurança de memória
  • Por exemplo, se uma CLI sobrescrever dados silenciosamente em uma situação de erro ou configurar permissões de arquivo de forma incorreta, o usuário pode enfrentar problemas graves
  • Segurança de ferramentas CLI

    • Comportamento previsível: é preciso garantir comportamento consistente e claro mesmo diante de entradas incorretas ou situações inesperadas
    • Prevenção de falhas e corrupção de dados: os erros devem ser tratados com elegância, evitando corrupção de dados ou falhas não informadas
    • Gestão de desempenho: mesmo ao processar grandes volumes de dados, não deve haver consumo excessivo de recursos nem perda de responsividade
    • Proteção de informações sensíveis: é preciso cuidado com arquivos temporários e configurações de permissão
    • Resistência a ataques: é necessário robustez contra validação inadequada de entrada, estouros de memória, ataques de injeção etc.

Pontos fortes e limitações do Borrow Checker do Rust

  • Pontos fortes

    • Bloqueio de data races e referências duplicadas: o compilador garante a regra de uma única referência mutável ou múltiplas referências imutáveis
    • Fortes garantias em tempo de compilação: a maioria dos bugs relacionados à memória é bloqueada antes da execução
    • Detecção precoce de bugs: isso traz grande vantagem em serviços comerciais ou sistemas concorrentes
  • Limitações e incômodos

    • Sobrecarga cognitiva: mesmo em tarefas pequenas de CLI, é inevitável se preocupar com ownership, lifetime e gerenciamento de referências
    • Boilerplate/distorção estrutural: wrappers como Rc e RefCell, uso excessivo de clone e redesenho estrutural acabam deslocando o foco de “resolver o problema” para “satisfazer o compilador”
    • Pouca utilidade contra bugs lógicos ou de estado: ele garante apenas regras de memória; previsibilidade, erros lógicos e integridade de dados não são garantidos
    • Complexidade em casos extremos: conflitos de lifetime surgem facilmente em cache, estado global, índices mutáveis etc.
  • Como resultado, em pequenos projetos CLI o borrow checker do Rust vira um “imposto mental” para o desenvolvedor e pode tornar tudo mais complexo do que o necessário

A abordagem do Zig para segurança e simplicidade

  • O Zig se baseia em verificações de segurança opcionais e gerenciamento manual de memória
  • Ele incorpora o conceito de allocator, permitindo implementar um uso de memória estruturado e previsível
  • Também é possível criar allocators customizados para definir a estratégia de gerenciamento de memória conforme as características do projeto
  • Graças à sintaxe defer do Zig, a liberação automática ao fim do escopo e a limpeza de recursos também ficam muito mais intuitivas
  • Diferentemente do Rust, o Zig enfatiza a responsabilidade do desenvolvedor, exigindo disciplina, mas quando a estrutura é bem projetada, fica fácil alcançar e manter a segurança de memória
  • O código em Zig é conciso, e mudanças em estruturas com ponteiros, listas e índices são muito mais simples do que em Rust
  • Sem as mesmas amarras do Rust, ainda é possível implementar código com nível equivalente de segurança e eficiência
  • Além disso, o recurso comptime do Zig ajuda bastante com execução de código em tempo de compilação, testes e otimizações

A importância da experiência do desenvolvedor (Developer Ergonomics)

  • Experiência do desenvolvedor (ergonomics) abrange fatores como sintaxe da linguagem, tooling, documentação e comunidade
  • O Rust garante segurança de memória no fim das contas graças a regras muito rígidas, mas o excesso de regras e ceremony acaba reduzindo a produtividade
  • O Zig enfatiza um design guiado pelo desenvolvedor, permitindo escrever, modificar e entender código com mais facilidade e rapidez
  • Com código intuitivo, iteração rápida e baixa carga mental, o Zig ajuda o desenvolvedor a focar em resolver problemas em vez de lutar com a ferramenta
  • O Zig confia no desenvolvedor e oferece ferramentas e opções adequadas, enquanto o Rust pode passar uma sensação excessivamente controladora e restritiva
  • Em vez de “proteger o desenvolvedor de cometer erros”, um ambiente amigável ao desenvolvedor garante a oportunidade de aprender e evoluir por meio dos próprios erros

Conclusão

  • Em áreas como sistemas grandes, multithread e de longa duração, onde as vantagens do Rust são maximizadas, Rust continua sendo a melhor escolha
  • Porém, para ferramentas CLI pequenas e práticas, a leveza, simplicidade e rapidez de implementação e manutenção do Zig são mais adequadas
  • Segurança de memória é apenas uma parte do quebra-cabeça da segurança; fatores essenciais para ferramentas CLI, como comportamento previsível, manutenibilidade e robustez, são mais fáceis de alcançar com Zig
  • No fim, o importante não é a “linguagem melhor”, e sim a escolha da ferramenta adequada ao meu workflow e às características do projeto
  • O Zig é uma linguagem que se encaixa perfeitamente no desenvolvimento de pequenas ferramentas ao combinar “segurança de memória + baixo custo mental + ergonomia/produtividade para o desenvolvedor

3 comentários

 
shakespeares 2025-10-05

Parece que o ecossistema ainda não está tão estabilizado quanto o do Rust.

 
bus710 2025-09-27

Como o Zig costuma ter mudanças incompatíveis com bastante frequência nas versões novas... mesmo em projetos pequenos, parece melhor, se possível, adicionar CI e continuar fazendo a manutenção.

 
GN⁺ 2025-09-27
Comentários do Hacker News
  • A vantagem do Zig é que ele permite continuar pensando como um desenvolvedor C, embora até certo ponto isso também seja apenas uma questão de familiaridade

    • Desenvolvedores suficientemente acostumados com Rust não ficam mais “brigando” com o borrow checker, porque já passam a pensar em estruturas de código compatíveis com ele

    • Em Rust, abordagens como “object soup” não funcionam muito bem, mas também não acho que isso seja fundamentalmente mais fácil; só parece mais fácil porque estamos acostumados

    • Se aceitarmos a premissa de que ergonomia é difícil de medir ou quantificar, discussões como essa ficam inevitavelmente nebulosas

      • É parecido com dizer: “esta cadeira não vai quebrar, então sente-se tranquilo, embora talvez ela seja um pouco menos confortável e mais pesada, mas a maioria das pessoas logo se acostuma e deixa de notar o desconforto”
      • Como na frase do texto original, dizer que “Rust oferece uma experiência de desenvolvimento menos confortável em troca de segurança de memória, enquanto Zig oferece uma experiência melhor e, com um pouco de cuidado, também pode oferecer segurança de memória” no fim das contas é um trade-off entre segurança e usabilidade
      • Acho que a comunidade Rust deveria reconhecer esse trade-off com honestidade; garantir segurança sempre vem acompanhado de menos conveniência
      • Isso está por trás de segurança, vida, cotidiano e software em geral, e muitas vezes os argumentos são vagos ou subjetivos
      • Também há quem descarte rapidamente o ponto da ergonomia dizendo “para quem está acostumado não tem problema”, o que pode soar como “se isso é difícil para você, então você é menos inteligente”
    • A ideia de “brigar com o borrow checker” vem da época em que as pessoas entendiam apenas lifetimes léxicos em Rust

      • Quando aprendi Rust em 2021, isso já era história antiga
      • De fato, para quem só usou Python, C ou JavaScript, adaptar-se ao Rust pode não ser fácil
      • No meu caso, foi algo natural, mas parece que a maioria das pessoas não sente isso
      • Só que dizer “em Rust você precisa ler as mensagens de diagnóstico e corrigir o código” não soa tão heroico quanto “lutar bravamente contra o borrow checker”; acho que deveríamos descrever a situação real de forma mais honesta
    • Pela minha experiência, desenvolvedores Rust experientes acabam espalhando Arc por todo lado e usando isso quase como garbage collection automática

      • O gerenciamento estático de memória é bastante rígido, então em estruturas de dados complexas isso se torna pouco prático
      • Por causa dos lifetimes, o caminho de raciocínio exigido ultrapassa com facilidade a capacidade cognitiva humana
    • Também já vi muitos projetos open source em Rust onde até desenvolvedores experientes usam Arc, Clone, Copy e afins por toda parte

    • A vantagem do Zig é ter recursos na linguagem e no tooling que ajudam com segurança, ao mesmo tempo em que permitem programar de forma familiar, como em C

      • Por exemplo, os optionals do Zig ajudam a evitar problemas de dereferência de nil
      • Em debug ou testes, também é fácil passar allocators de debug/customizados diretamente para fazer verificações em runtime, monitorar acessos à memória e checar vazamentos de recursos
      • Tenho ressalvas quanto à ausência de interfaces/traits explícitas, mas acho que a simplicidade de adoção o torna prático
  • Eu discordo da maior parte do texto original

    • Rust, assim como C ou Zig, ainda exige pensar em lifetime, ownership e escopo de borrow; a diferença é ter ou não ajuda do compilador

    • Por mais inteligente que alguém seja, seres humanos erram quando estão cansados ou distraídos; reconhecer isso é sinal de sabedoria

    • O espaço de programas que o compilador Rust considera seguros não é amplo o bastante, então ele rejeita programas perfeitamente válidos com certa frequência

    • Exemplo: se numa struct Foo, bar e baz forem strings, ao pegar uma referência mutável para bar e depois uma referência imutável para baz, o código não compila, e nessas situações você acaba sendo forçado a contornar a estrutura do código

    • Em resposta a isso, o fato de você ter de mudar seu código para uma segunda ou terceira melhor arquitetura só para evitar casos em que “na prática está tudo bem, mas a compilação é rejeitada” já é um custo grande

      • Isso pode alterar o design de toda a base de código
      • Isso ajuda a explicar por que a adoção de Rust pesa para quem faz trabalhos difíceis, como desenvolvedores de jogos
      • Se o compilador Rust funcionasse com precisão perfeita e sem falsos positivos, ele seria extraordinário também em ergonomia, embora a realidade esteja longe de ser tão simples
    • O exemplo acima parece realmente excelente; queria perguntar se posso usá-lo no meu blog ou artigo

    • Ao olhar esse código, na verdade fiquei ainda menos confiante na tese

  • Precisamos lembrar que nem todo programa precisa necessariamente ser tão “seguro” assim

    • Crescemos nos divertindo com muito software unsafe: Star Fox 64, MS Paint, FruityLoops etc.

    • Também li que Andrew Kelley, criador do Zig, fez a linguagem porque não havia um bom ambiente para desenvolver software de produção musical (DAW), e acho que Zig combina bem com esse tipo de software criativo

    • Se alguém é muito sensível a bugs de memória, pode usar Rust

    • Também acredito que Super Mario World era mais divertido por causa dos bugs de memória

    • “Segurança” é uma abreviação para “meu programa se comporta como eu pretendia”

      • Código não intencional e sem lógica semântica é ruim para atingir esse objetivo
      • Há casos em que se escreve código deliberadamente obscuro por motivos artísticos (IOCCC, hacking, poesia em código), mas aí isso precisa ser feito com cuidado
      • Em Rust também é possível implementar esse tipo de coisa de propósito por meio de uma escape hatch (unsafe)
      • O argumento do texto é que programar de forma acidentalmente sem sentido seria uma vantagem, e isso é difícil de aceitar
      • Se fosse possível tornar todo código seguro sem desvantagens, quem não iria querer isso?
      • Speedruns de Super Mario World e afins também poderiam ser feitos com patches binários; não acho que manipular memória por entrada seja a única graça possível
    • Fiquei um pouco confuso: você acha que minha opinião é ruim porque ela implicaria que segurança de memória não é importante?

  • Achei uma pena que o valor do borrow checker tenha sido subestimado

    • O borrow checker do Rust garante, em tempo de compilação, a ausência de acessos inválidos à memória

    • Claro, existe a inconveniência de ter de reorganizar o código para obedecer às regras do compilador

    • Quando usei Rust separadamente, nunca achei que as lifetime annotations estivessem “erradas”; pareciam mais uma tarefa chata, mas à qual se acostuma rápido

    • A menos que se use unsafe, Rust não permite que duas threads escrevam na mesma memória ao mesmo tempo

    • Não consigo concordar com a ideia de “por que Zig parece mais prático para ferramentas CLI”; Rust ainda tem uma vantagem real na prevenção de CVEs

    • Na prática, eu mesmo faço a maior parte do meu trabalho em linguagens com GC, e quando contribuo em outras linguagens tanto faz para mim se é Rust, Zig ou C/C++

    • Ferramentas CLI seriam algo especial?

      • Em geral, ferramentas CLI não são tão grandes ou são desenvolvidas individualmente, então são mais fáceis de manter
      • Zig ou C podem não combinar tão bem com codebases grandes; em projetos mais complexos, é preciso um “babá”
      • Houve discussão parecida no passado, quando Java também era chamada de “linguagem babá”, mas em muitas codebases isso realmente é necessário
    • A afirmação de que sem unsafe duas threads não podem escrever na mesma memória ao mesmo tempo não é tão limpa assim

      • O suporte que eu mais queria do compilador é para resolver questões de memory ordering
      • Em Rust, mesmo que isso gere race em certo sentido, ainda é classificado como safe, não como unsafe
  • Concordo que implementar backlinks em Rust é complicado demais

    • Dá para fazer usando Rc, Weak, RefCell, .borrow() etc., mas não é fácil

    • Se o programa for de curta duração, alocação em arena também é uma opção, o que aparentemente se encaixa no que se quer dizer com ferramentas CLI

    • Rust realmente mostra seu valor em aplicações enormes, multithread e de longa execução

    • Já escrevi um grande cliente de metaverso em Rust, e mesmo rodando dezenas de threads por 24 horas não tive vazamentos de memória nem crashes

    • Para fazer o mesmo em C++, uma equipe de QA e ferramentas como Valgrind seriam indispensáveis, e linguagens de script seriam lentas demais em termos de desempenho

    • Eu também já fiz em Rust uma aeronave de simulação física que levava em conta até a curvatura da Terra e variações gravitacionais

      • Rodei testes de trajeto de 5 horas e 22 horas por anos sem nenhum problema
      • Em 7 anos de Rust, tive pouquíssimos crashes; C/C++ hoje só uso quando preciso corrigir código legado
  • Zig é atraente, mas D ainda existe, e pessoalmente sinto que D é o substituto de C/C++ que eu gostaria de ter

    • A sintaxe do Zig me parece um pouco estranha, e Rust já se tornou central no ecossistema

    • Go também ocupa muito espaço nas ferramentas de várias linguagens e, na área de IA, é provavelmente a mais usada depois de Python

    • Antes do Rust, havia discussões de Go vs. D, e eu cheguei até a comprar um livro de D antes de migrar para Go

      • Para mim, a biblioteca padrão era muito mais prática, e nomes de tipos como int64 eram mais intuitivos
    • D é bom, mas nunca apareceu um killer app que o popularizasse

  • Não entendo muito bem por que seria preciso usar Rust ou Zig especificamente para ferramentas CLI

    • O gargalo é I/O, não o fato de GC ser lento

    • Acho que a questão do GC só entra em pauta em software intensivo em memória, como jogos e bancos de dados

    • Mais do que discutir segurança de memória, eu costumo pensar por que se deveria escolher uma linguagem sem GC

    • Se a resposta for “porque programar sem GC é divertido”, isso por si só já basta; não precisa de debate

    • O startup time imediato, sem atraso na execução, é muito útil

      • Ao criar ferramentas wrapper, você pode executá-las milhares de vezes sem incômodo
      • A distribuição também fica mais fácil, sem a complexidade de deployment de ambientes como o Python
    • Fazer CLI em Go foi uma experiência muito boa para mim (embora eu não goste muito da linguagem Go em si)

      • CLIs em Python ficam chatas de distribuir quando têm muitas dependências, e Rust/Zig, assim como Go, são populares porque permitem distribuir binários estáticos com facilidade
    • Eu priorizo linguagens com sum types, pattern matching e suporte a async

      • Mesmo fora do Rust, gosto de recursos que capturam erros em tempo de compilação
    • Sobre a ideia de que desenvolvimento sem GC só importa para jogos

      • Na prática, muitos jogos mobile também usam GC, como em ambientes Unity + il2cpp, e muitas vezes o desempenho do GC nem é tão bom assim
    • O debate sobre GC tem um certo efeito manada

      • Já há 50 anos linguagens com GC como Interlisp e Cedar eram usadas com sucesso em grandes workstations
      • Hoje o hardware é muito superior ao das CLIs dos anos 1970 ou aos apps Electron, mas ainda o aproveitamos mal
  • Fiz uma ferramenta simples de notas em Rust usando o borrow/referencing embutido, e não achei tão complexo quanto parece

    • Se você imaginar uma estrutura que guarda índices de uma lista de notes e os conecta por meio de um mapa, quase não há diferença de velocidade e não há desvantagem de segurança

    • Mesmo se você errar o índice, isso vira um erro de acesso fora dos limites, o que é muito melhor do que sobrescrever memória do kernel

    • Até para debugging com printf, fica muito mais fácil e intuitivo

    • Normalmente, raw pointers ou references eu deixo só para onde são realmente necessários, como allocator ou async runtime; para a lógica geral, uma abordagem baseada em índices faz mais sentido

    • É exatamente por isso, de forma conhecida, que o async do Rust não consegue usar self-referential struct e surgem as questões envolvendo Pin

    • Ponteiros para valores armazenados em vec ficam inválidos quando ocorre realloc e afins; nesses casos o Miri acusa erro imediatamente

  • Como desenvolvedor C++, se eu fosse procurar uma linguagem segura, Swift me pareceria a mais adequada

    • É mais fácil se adaptar rápido a uma linguagem familiar ou parecida

    • O Swift reforçou bastante o suporte cross-platform recentemente, e várias pessoas do comitê de padronização de C++ participam dele

    • Mas, por sua ligação com a Apple e a ausência de frameworks nativos de UI mais fortes fora desse ecossistema, sua expansão no campo não Apple é relativamente menor

    • Espero que Swift fique mais popular

    • Se alguém tiver recursos comparando Swift com Zig/C, eu agradeceria recomendações

  • Há quem diga que Zig também permite criar software memory-safe com um pouco de cuidado, mas na verdade isso também vale para C se for usado com disciplina suficiente

    • No fim, esse “pouco de disciplina” é justamente o que falta no mundo real, e é daí que os problemas surgem

    • Além disso, Zig resolve os problemas abaixo

      • Acesso fora dos limites (70% de todos os CVEs)
      • dereference de ponteiro nulo
      • Segurança de tipos
      • É muito superior ao C, e erros como use-after-free também são bem mais fáceis de evitar, desde que você não atravesse fronteiras indevidas
      • O excelente sistema de build cross-platform do Zig, otimizações em comptime e tempos de build dezenas de vezes mais rápidos que C++/Rust também são pontos fortes
      • A biblioteca padrão ainda é fraca e restam pequenos problemas, mas acho que o futuro é promissor para programas orientados a performance
    • Se o C falhou por mais de 50 anos em resolver esse problema de disciplina, então isso é mais difícil do que o “caminho do monge Shaolin”