Esquecer defer — Como Resolver em Zig
O Que Este Erro Significa
Esquecer de usar defer para liberar recursos é um dos erros mais comuns para iniciantes em Zig. Diferente de linguagens com garbage collector, Zig requer que o programador gerencie manualmente a vida útil de cada recurso alocado. O defer é a ferramenta principal para garantir que recursos sejam liberados ao sair de um escopo, independente de como a saída acontece (retorno normal, retorno antecipado ou propagação de erro).
Sem defer, o código fica vulnerável a vazamentos de memória, descritores de arquivo não fechados, mutexes não liberados e outros problemas que se acumulam silenciosamente.
Causas Comuns
1. Alocar Memória sem defer free
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const dados = try allocator.alloc(u8, 1024);
// ERRO: sem defer allocator.free(dados)
// ... uso dos dados ...
// Ao sair de main, memória vaza
}
2. Abrir Arquivo sem defer close
const std = @import("std");
pub fn main() !void {
const file = try std.fs.cwd().openFile("dados.txt", .{});
// ERRO: sem defer file.close()
var buf: [1024]u8 = undefined;
const bytes = try file.read(&buf);
_ = bytes;
// Descritor de arquivo vaza ao sair
}
3. Inicializar Coleção sem defer deinit
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var lista = std.ArrayList(u32).init(allocator);
// ERRO: sem defer lista.deinit()
try lista.append(1);
try lista.append(2);
// Memória interna da lista vaza
}
4. Retorno Antecipado sem Limpeza
const std = @import("std");
fn processar(allocator: std.mem.Allocator) !void {
const buf = try allocator.alloc(u8, 1024);
// Sem defer!
if (buf.len < 2048) {
return error.BufferPequeno;
// LEAK: buf nunca é liberado neste caminho
}
allocator.free(buf); // Só libera no caminho de sucesso
}
5. Mutex sem defer unlock
const std = @import("std");
var mutex = std.Thread.Mutex{};
var dados_compartilhados: u32 = 0;
fn acessar() void {
mutex.lock();
// ERRO: sem defer mutex.unlock()
dados_compartilhados += 1;
// Se uma exceção/panic acontecer, mutex fica travado para sempre
mutex.unlock(); // Só funciona se chegar aqui
}
Como Corrigir
Solucao 1: Padrão alloc + defer free
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const dados = try allocator.alloc(u8, 1024);
defer allocator.free(dados); // Sempre na linha seguinte ao alloc!
// Usa dados com segurança
dados[0] = 42;
// defer cuida do free automaticamente
}
Solucao 2: Padrão open + defer close
const std = @import("std");
pub fn main() !void {
const file = try std.fs.cwd().openFile("dados.txt", .{});
defer file.close(); // Sempre após abrir!
var buf: [1024]u8 = undefined;
const bytes = try file.read(&buf);
std.debug.print("Lidos {} bytes\n", .{bytes});
// file é fechado automaticamente
}
Solucao 3: Padrão init + defer deinit
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var lista = std.ArrayList(u32).init(allocator);
defer lista.deinit(); // Imediatamente após init!
var mapa = std.StringHashMap(u32).init(allocator);
defer mapa.deinit(); // Imediatamente após init!
try lista.append(42);
try mapa.put("chave", 100);
}
Solucao 4: errdefer para Limpeza em Caso de Erro
const std = @import("std");
fn criarRecurso(allocator: std.mem.Allocator) ![]u8 {
const buf = try allocator.alloc(u8, 1024);
errdefer allocator.free(buf); // Libera SÓ se retornar erro
try validar(buf);
// Se validar falhar, errdefer libera buf
// Se sucesso, quem chamou é responsável
return buf; // Transfere propriedade
}
fn validar(buf: []u8) !void {
_ = buf;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const recurso = try criarRecurso(allocator);
defer allocator.free(recurso); // Quem recebeu libera
}
Solucao 5: Padrão lock + defer unlock
const std = @import("std");
var mutex = std.Thread.Mutex{};
var contador: u32 = 0;
fn incrementar() void {
mutex.lock();
defer mutex.unlock(); // Sempre após lock!
contador += 1;
// Mesmo que algo dê errado, mutex é liberado
}
Solucao 6: Múltiplos defers (Ordem LIFO)
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const a = try allocator.alloc(u8, 100);
defer allocator.free(a); // Executado por último
const b = try allocator.alloc(u8, 200);
defer allocator.free(b); // Executado segundo
const file = try std.fs.cwd().openFile("dados.txt", .{});
defer file.close(); // Executado primeiro
// Ordem de execução dos defers: file.close, free(b), free(a), gpa.deinit
// Isso é LIFO (Last In, First Out) — ordem inversa de declaração
}
Regra de Ouro
Imediatamente após adquirir um recurso, escreva o defer para liberá-lo.
// PADRÃO CORRETO:
const recurso = try adquirir();
defer liberar(recurso);
// ... usar recurso ...
Nunca escreva código entre a aquisição e o defer:
// PADRÃO PERIGOSO:
const recurso = try adquirir();
try fazerAlgo(); // Se falhar, recurso vaza!
defer liberar(recurso); // Tarde demais
Lista de Padrões defer Comuns
| Aquisição | defer Correspondente |
|---|---|
allocator.alloc(...) | defer allocator.free(...) |
allocator.create(T) | defer allocator.destroy(ptr) |
file.open(...) | defer file.close() |
ArrayList.init(...) | defer lista.deinit() |
HashMap.init(...) | defer mapa.deinit() |
mutex.lock() | defer mutex.unlock() |
ArenaAllocator.init(...) | defer arena.deinit() |
GPA{} | defer _ = gpa.deinit() |
defer vs errdefer
// defer: executa SEMPRE ao sair do escopo
defer allocator.free(buf);
// errdefer: executa APENAS se a função retornar erro
errdefer allocator.free(buf);
Use errdefer quando a função retorna o recurso ao chamador em caso de sucesso:
fn criar(allocator: std.mem.Allocator) ![]u8 {
const buf = try allocator.alloc(u8, 100);
errdefer allocator.free(buf); // SÓ se houver erro
try preencher(buf);
return buf; // Sucesso: chamador recebe e é responsável
}
Erros Relacionados
- Memory leak — Vazamento de memória
- Allocator leak detected — Leak detectado pelo GPA
- Double free — Liberar recurso duas vezes
- Use after free — Usar recurso após liberar