Errdefer em Zig — O que é e Como Usar
Definição
O errdefer em Zig é uma variante do defer que executa o código agendado apenas quando o escopo é abandonado por causa de um erro. Se a função retorna com sucesso, o errdefer é ignorado. Esse mecanismo é fundamental para construir funções que alocam múltiplos recursos e precisam desfazer alocações parciais em caso de falha.
Por que Errdefer Importa
- Alocação parcial segura: Quando uma função aloca vários recursos em sequência,
errdefergarante que os já alocados sejam liberados se um dos passos falhar. - Evita vazamentos de memória: Sem
errdefer, erros no meio de uma função podem deixar recursos sem liberação. - Código mais legível: A lógica de limpeza fica junto da alocação, não em blocos de tratamento de erro distantes.
- Complementa o
defer: Enquantodeferé para limpeza incondicional,errdeferé para reverter operações.
Exemplo Prático
Padrão de Inicialização Segura
const std = @import("std");
const Servidor = struct {
socket: std.posix.socket_t,
buffer: []u8,
log: std.fs.File,
pub fn init(allocator: std.mem.Allocator) !Servidor {
// Passo 1: abrir socket
const socket = try std.posix.socket(std.posix.AF.INET, std.posix.SOCK.STREAM, 0);
errdefer std.posix.close(socket); // Fecha se passos seguintes falharem
// Passo 2: alocar buffer
const buffer = try allocator.alloc(u8, 4096);
errdefer allocator.free(buffer); // Libera se passo 3 falhar
// Passo 3: abrir arquivo de log
const log = try std.fs.cwd().createFile("servidor.log", .{});
// Sem errdefer aqui — se chegamos aqui, tudo deu certo
return Servidor{
.socket = socket,
.buffer = buffer,
.log = log,
};
}
pub fn deinit(self: *Servidor, allocator: std.mem.Allocator) void {
self.log.close();
allocator.free(self.buffer);
std.posix.close(self.socket);
}
};
Se createFile falhar, o buffer é liberado e o socket é fechado automaticamente. Se alloc falhar, apenas o socket é fechado. Cada errdefer protege contra falhas nos passos subsequentes.
Errdefer com Captura de Erro
A partir de versões recentes do Zig, errdefer pode capturar o erro:
fn processar() !void {
const recurso = try adquirirRecurso();
errdefer |err| {
std.log.err("Falha ao processar: {}", .{err});
recurso.liberar();
};
try etapa1();
try etapa2();
try etapa3();
}
A variável err contém o erro que causou a saída do escopo, permitindo logging detalhado.
Quando Usar Defer vs Errdefer
fn exemplo(allocator: std.mem.Allocator) !*Dados {
const dados = try allocator.create(Dados);
// ERRADO: defer destruiria mesmo no retorno com sucesso!
// defer allocator.destroy(dados);
// CERTO: errdefer só destrói se houver erro
errdefer allocator.destroy(dados);
dados.* = try Dados.inicializar();
return dados; // Chamador agora é dono da memória
}
A regra é simples: se a função retorna o recurso ao chamador, use errdefer. Se a função consome o recurso internamente, use defer.
Armadilhas Comuns
- Usar
deferonde deveria sererrdefer: Se a função retorna um recurso alocado,defero destruiria antes do chamador usá-lo. - Esquecer o
errdeferem cadeias de alocação: Cada recurso alocado antes de um possíveltryprecisa de seu próprioerrdefer. - Ordem dos errdefers: Assim como
defer, oserrdefers executam em ordem LIFO. Certifique-se de que a ordem de limpeza faz sentido. - Não usar quando não há transferência de propriedade: Se o recurso será liberado na mesma função,
deferé suficiente.
Termos Relacionados
- Defer — Execução garantida ao sair do escopo
- Error Union — Tipo que carrega valor ou erro
- Try — Propagação automática de erros
- Allocator — Abstração de alocação de memória
- RAII em Zig — Padrão de gerenciamento de recursos