---
title: "Checklist de Code Review em Zig: Memória, Erros, Segurança e Performance"
url: "https://ziglang.com.br/artigos/zig-code-review-checklist/"
markdown_url: "https://ziglang.com.br/artigos/zig-code-review-checklist.MD"
description: "Checklist prático de code review em Zig para revisar allocators, defer/errdefer, error unions, comptime, testes, segurança, performance e releases."
date: "2026-02-21"
author: "Zig Brasil"
---

# Checklist de Code Review em Zig: Memória, Erros, Segurança e Performance

Checklist prático de code review em Zig para revisar allocators, defer/errdefer, error unions, comptime, testes, segurança, performance e releases.


Code review em Zig não é só procurar estilo. A linguagem dá controle explícito sobre memória, erros, layout, compilação, integração com C e artefatos finais. Esse controle é ótimo para software de sistemas, CLIs, servidores e bibliotecas de performance, mas também muda o tipo de bug que passa por uma revisão superficial.

Um review bom em Zig responde perguntas concretas: quem é dono dessa memória? O erro libera tudo que já foi alocado? O allocator correto chega até a camada certa? O `comptime` facilita ou esconde complexidade? O binário foi testado no target que será distribuído? A dependência nova alterou a supply chain?

Este checklist é uma base prática para revisar pull requests em projetos Zig. Use como roteiro, não como burocracia. Para contexto complementar, veja também [Clean Code em Zig](/artigos/zig-clean-code/), [Error Handling em Zig](/artigos/zig-error-handling-boas-praticas/), [Alocação de Memória](/artigos/zig-alocacao-memoria-estrategias/), [Testes em Zig](/artigos/zig-testes-guia-completo/) e [Zig Supply Chain](/artigos/zig-supply-chain-dependencias-releases/).

## 1. Comece pelo contrato da mudança

Antes de entrar linha por linha, entenda o que a mudança promete:

- adiciona API pública, comando de CLI, endpoint ou biblioteca interna?
- muda comportamento compatível ou quebra contrato?
- toca hot path, parser, rede, arquivo, criptografia ou memória compartilhada?
- roda em produção, build script, teste ou ferramenta local?
- afeta targets específicos, como Linux, macOS, Windows, ARM ou WebAssembly?

Em Zig, contexto importa muito. Uma alocação aceitável em comando administrativo pode ser ruim em loop de rede. Um `panic` tolerável em build tool pode ser inaceitável em biblioteca. Um `@ptrCast` em camada de interoperabilidade C pode ser necessário; espalhado pelo domínio da aplicação, provavelmente é sinal de design frágil.

## 2. Gerenciamento de memória

A primeira pergunta de review é propriedade: quem aloca, quem libera e por quanto tempo o dado vive?

```zig
const dados = try allocator.alloc(u8, 1024);
defer allocator.free(dados);

const copia = try allocator.dupe(u8, entrada);
errdefer allocator.free(copia);
try registrar(copia);
```

Verifique:

- todo `alloc`, `create`, `dupe` ou `ArrayList.init` tem `free`, `destroy` ou `deinit` correspondente;
- caminhos de erro usam `errdefer` quando a função ainda não transferiu propriedade;
- structs com `init` têm `deinit` claro e documentado;
- funções que retornam memória alocada dizem qual allocator deve liberar;
- slices retornados não apontam para stack frame encerrado;
- `ArenaAllocator` não está escondendo vazamento em processo longo;
- `GeneralPurposeAllocator` ou `std.testing.allocator` é usado em testes para detectar leaks.

Um padrão saudável é nomear propriedade no contrato:

```zig
/// Retorna uma string alocada com `allocator`. O chamador deve liberar.
pub fn renderMensagem(allocator: std.mem.Allocator, nome: []const u8) ![]u8 {
    return try std.fmt.allocPrint(allocator, "Olá, {s}", .{nome});
}
```

Se o review precisa adivinhar quem libera, o código precisa de ajuste.

## 3. defer, errdefer e ordem de limpeza

`defer` e `errdefer` são fortes, mas ordem importa. Eles executam em ordem inversa de declaração. Em funções com vários recursos, confira se a limpeza acontece no sentido correto.

```zig
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();

var buffer = try allocator.alloc(u8, size);
errdefer allocator.free(buffer);

try file.readAll(buffer);
return buffer; // propriedade transferida; errdefer não roda no sucesso
```

Perguntas de review:

- `defer` libera algo que ainda será usado depois?
- `errdefer` deveria virar `defer` porque não há transferência de propriedade?
- há múltiplos recursos que precisam ser liberados em ordem específica?
- uma função retorna antes de registrar o `defer` necessário?
- um objeto parcialmente inicializado tem `errdefer` para cada campo já adquirido?

Para inicialização de structs complexas, prefira etapas pequenas e limpeza explícita. O bug clássico é alocar o segundo campo, falhar no terceiro e vazar o primeiro.

## 4. Tratamento de erros sem engolir falhas

Zig força error unions, mas ainda é possível destruir observabilidade com `catch {}` ou transformar tudo em erro genérico.

```zig
// Fraco: o erro desaparece.
_ = gravarArquivo() catch {};

// Melhor: contexto, propagação ou fallback explícito.
gravarArquivo() catch |err| {
    std.log.err("falha ao gravar relatório {s}: {}", .{ caminho, err });
    return err;
};
```

Revise:

- `try` está no nível certo ou deveria adicionar contexto antes de propagar?
- `catch unreachable` é realmente impossível ou só conveniente?
- erros de input externo viram resposta controlada, não `panic`;
- bibliotecas não chamam `std.process.exit` ou `panic` para erro recuperável;
- logs têm contexto suficiente sem expor segredo;
- error sets públicos são estáveis e compreensíveis.

Em APIs públicas, cuidado com error set enorme que vaza detalhe interno. Em aplicações, cuidado com erro genérico que não ajuda a operar produção.

## 5. Allocator correto para cada camada

Allocator é decisão arquitetural. Em review, confira se o allocator vem de fora quando a função é reutilizável:

```zig
pub fn parseConfig(allocator: std.mem.Allocator, bytes: []const u8) !Config {
    // bom: chamador escolhe arena, GPA, testing allocator etc.
}
```

Evite criar allocator global escondido em biblioteca. Em servidor, prefira separar:

- allocator de processo para estruturas de longa vida;
- arena por request ou por job quando tudo morre junto;
- buffers reutilizáveis em hot path;
- `std.testing.allocator` em testes.

Se a mudança troca `ArenaAllocator` por alocações individuais, peça justificativa. Se troca alocações individuais por arena, confira se nenhum ponteiro da arena escapa além do ciclo de vida.

## 6. Slices, ponteiros e aliasing

Muitos bugs de Zig aparecem como slice válido com vida errada. Revise:

- slice retornado aponta para buffer de chamador ou memória própria?
- função modifica slice que parece somente leitura?
- `[]u8` deveria ser `[]const u8`?
- ponteiros opcionais (`?*T`) são checados antes de uso?
- `@ptrCast`, `@alignCast` e `@constCast` estão isolados e justificados?
- há suposição de alinhamento ou endianess documentada?

Prefira reduzir área insegura:

```zig
const bytes: []const u8 = std.mem.asBytes(&header);
```

Quando `@ptrCast` for necessário, deixe o código próximo da validação de tamanho/alinhamento e cubra com teste.

## 7. comptime e generics legíveis

`comptime` é uma das melhores partes de Zig, mas review deve separar abstração útil de metaprogramação decorativa.

Pergunte:

- o `comptime` reduz duplicação real ou só torna erro mais difícil?
- mensagens de erro para uso incorreto são claras?
- há `@compileError` amigável quando o tipo não atende ao contrato?
- código gerado mantém tamanho de binário aceitável?
- testes cobrem pelo menos dois tipos/instâncias relevantes?

Exemplo de contrato melhor:

```zig
fn Repository(comptime T: type) type {
    if (!@hasDecl(T, "id")) {
        @compileError("Repository(T) exige campo ou decl id");
    }
    return struct { /* ... */ };
}
```

Se o código usa `@typeInfo`, revise com calma. Bugs de comptime costumam ser raros, mas quando quebram, a mensagem pode atingir todo consumidor da biblioteca.

## 8. Interoperabilidade com C

Zig chama C com facilidade. O review precisa tratar essa fronteira como área de risco.

Confira:

- headers importados por `@cImport` são estáveis e versionados;
- tipos C têm conversão explícita para tipos Zig;
- strings C terminadas em zero são validadas;
- ownership de ponteiros C está documentado;
- funções C que retornam código de erro são checadas;
- biblioteca linkada é a esperada no target final;
- build.zig não depende de caminho local do autor.

Se um ponteiro vem de C, quem libera? Se Zig passa callback para C, qual é a vida do contexto? Essas duas perguntas pegam muitos problemas.

## 9. Performance e hot paths

Zig atrai projetos de performance, mas review não deve aceitar micro-otimização sem evidência. Procure primeiro problemas óbvios:

- alocação dentro de loop apertado;
- cópia desnecessária de buffers grandes;
- formatação de string em caminho quente;
- lookup linear onde mapa ou índice simples resolveria;
- locks ou atomics sem necessidade;
- branch complexa em parser crítico;
- `ReleaseFast` escondendo comportamento que deveria ser seguro.

Exemplo típico:

```zig
var buffer = std.ArrayList(u8).init(allocator);
defer buffer.deinit();

for (items) |item| {
    buffer.clearRetainingCapacity();
    try buffer.appendSlice(item);
    try processar(buffer.items);
}
```

Se a mudança afirma ganho de performance, peça benchmark reproduzível. O guia de [benchmarking em Zig](/artigos/zig-benchmarking-medir-performance/) e o material de [depuração e profiling](/artigos/zig-depuracao-profiling-tracy-valgrind-perf/) ajudam a validar sem chute.

## 10. Segurança de input, logs e segredos

Revise todo ponto onde dado externo entra: argumentos de CLI, arquivo, rede, variável de ambiente, JSON, header HTTP, path, payload binário ou resposta de processo.

Checklist:

- tamanhos são limitados antes de alocar;
- parser diferencia erro de formato, EOF e limite excedido;
- paths não permitem traversal acidental;
- logs não imprimem token, senha, cookie, chave ou DSN;
- mensagens de erro externas não vazam detalhe sensível;
- dados binários não são tratados como UTF-8 sem validação;
- valores de ambiente obrigatórios são validados no boot;
- defaults inseguros não entram em produção.

Para configuração, conecte o review ao guia de [segredos e variáveis de ambiente em Zig](/artigos/zig-configuracao-segura-segredos-env/). Para dependências e releases, conecte ao guia de [supply chain](/artigos/zig-supply-chain-dependencias-releases/).

## 11. Testes que realmente protegem

Procure teste no mesmo nível do risco:

- unidade para função pura;
- teste com `std.testing.allocator` para memória;
- fixture para parser;
- integração para arquivo, rede ou CLI;
- regressão para bug corrigido;
- teste por target quando a mudança é multiplataforma.

Um teste útil em Zig costuma verificar também erro e limpeza:

```zig
test "parse rejeita entrada grande" {
    const allocator = std.testing.allocator;
    const entrada = try allocator.alloc(u8, 1024 * 1024);
    defer allocator.free(entrada);
    try std.testing.expectError(error.InputTooLarge, parse(allocator, entrada));
}
```

Se a mudança mexe em memória, rode testes com allocator de teste. Se mexe em compilação cruzada, rode pelo menos build do target relevante. Se mexe em CLI, capture saída e exit code.

## 12. Build, formatação e CI

Antes de aprovar, procure evidência de comandos básicos:

```sh
zig fmt --check src build.zig
zig build test
zig build -Doptimize=ReleaseSafe
```

Dependendo do projeto, acrescente:

```sh
zig build -Dtarget=x86_64-linux-musl
zig build -Dtarget=aarch64-macos
zig build docs
```

Revise também o `build.zig`:

- opções têm nomes claros;
- targets e optimize vêm de `standardTargetOptions` e `standardOptimizeOption` quando apropriado;
- testes estão ligados ao step padrão esperado;
- paths não são absolutos da máquina do autor;
- flags inseguras são comentadas;
- dependências novas aparecem no `build.zig.zon` com hash.

## 13. Documentação e exemplos

Mudança de API sem exemplo vira dívida. Verifique:

- README ou docs mostram o caminho feliz;
- exemplo compila com a versão atual;
- nomes e comentários batem com comportamento real;
- limitações são explícitas;
- breaking change aparece em changelog;
- tutorial não recomenda `catch unreachable` ou allocator global sem explicar contexto.

Em Zig, exemplos quebrados doem mais porque a linguagem e o tooling ainda evoluem. Um snippet antigo pode confundir iniciante rapidamente.

## 14. Supply chain da própria mudança

Para PRs que alteram dependências, CI, release ou Docker, faça review operacional:

- versão do Zig está fixada;
- `build.zig.zon` usa URL e hash coerentes;
- actions não usam `latest` sem motivo;
- imagem Docker tem tag ou digest;
- artefatos de release têm checksum;
- token de publicação só existe no job de publicação;
- SBOM ou inventário é atualizado quando exigido;
- changelog cita dependência ou toolchain nova.

Esse bloco conversa diretamente com [GitHub Actions para releases Zig](/artigos/zig-github-actions-release-multiplataforma/) e [Zig Supply Chain](/artigos/zig-supply-chain-dependencias-releases/). Code review não termina no `.zig`; o build também é código.

## 15. Checklist rápido para colar no PR

Use este resumo quando precisar revisar rápido:

- [ ] propriedade de memória clara;
- [ ] `defer`/`errdefer` cobre sucesso e erro;
- [ ] sem slice apontando para vida encerrada;
- [ ] error handling preserva contexto;
- [ ] allocator vem do chamador quando a função é reutilizável;
- [ ] `@ptrCast`, `@alignCast`, `@constCast` são necessários e isolados;
- [ ] input externo tem limite e validação;
- [ ] logs não expõem segredo;
- [ ] testes cobrem caminho feliz, erro e regressão;
- [ ] `zig fmt` e `zig build test` rodaram;
- [ ] build.zig/build.zig.zon não introduzem caminho local ou dependência flutuante;
- [ ] documentação e exemplos foram atualizados;
- [ ] mudança de performance tem benchmark ou justificativa mensurável;
- [ ] release/CI continua reproduzível.

## Comparação com Rust e Go

Em Rust, o borrow checker captura parte dos problemas de memória antes do review. Em Go, garbage collector e convenções simples reduzem a superfície de ownership manual. Zig fica em outro ponto: dá controle explícito sem esconder custo. Por isso, o review humano precisa olhar propriedade, lifetime, erro e build com mais disciplina.

Isso não torna Zig "mais perigoso" por definição. Torna a revisão mais parecida com engenharia de sistemas: menos fé no framework, mais clareza no contrato. Para comparar práticas, veja comunidades de <a href="https://rustlang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust no Brasil</a> e <a href="https://golang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go no Brasil</a>.

## Conclusão

Code review em Zig é uma ferramenta de design. Ele confirma se a mudança tem ownership claro, erros previsíveis, testes úteis, build reproduzível e fronteiras seguras com C, sistema operacional e dependências.

Não tente revisar tudo com o mesmo peso. Foque primeiro no risco: memória, input externo, concorrência, build, release e API pública. Depois ajuste estilo. Um PR bonito que vaza memória, engole erro ou publica binário irreproduzível ainda não está pronto.

## Conteúdo relacionado

- [Clean Code em Zig](/artigos/zig-clean-code/) — princípios de clareza e manutenção.
- [Error Handling em Zig](/artigos/zig-error-handling-boas-praticas/) — tratamento de erros sem esconder falhas.
- [Alocação de Memória](/artigos/zig-alocacao-memoria-estrategias/) — estratégias de allocator e ownership.
- [Testes em Zig](/artigos/zig-testes-guia-completo/) — testes unitários, integração e regressão.
- [Depuração e Profiling em Zig](/artigos/zig-depuracao-profiling-tracy-valgrind-perf/) — validar bugs e performance.
- [Zig Supply Chain](/artigos/zig-supply-chain-dependencias-releases/) — revisar dependências, hashes e releases.
