Defer em Zig — O que é e Como Usar
Definição
O defer em Zig é uma instrução que agenda a execução de um trecho de código para quando o escopo atual terminar, independentemente de como ele termina — seja por retorno normal, erro ou qualquer outro fluxo. É o mecanismo principal em Zig para garantir que recursos sejam liberados de forma confiável.
Pense no defer como uma promessa: “não importa o que aconteça, execute isso antes de sair daqui”.
Por que Defer Importa
- Gerenciamento de recursos: Garante que memória, arquivos e conexões sejam liberados.
- Código mais limpo: A alocação e a desalocação ficam próximas no código, facilitando a leitura.
- Segurança contra erros: Mesmo que uma função retorne um erro com
try, odeferainda executa. - Substituição do RAII: Em Zig não há destrutores automáticos;
defercumpre esse papel.
Exemplo Prático
Uso Básico
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); // Executado ao sair de main
const allocator = gpa.allocator();
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer); // Liberado mesmo se houver erro abaixo
// Usar buffer aqui...
std.debug.print("Buffer alocado com {} bytes\n", .{buffer.len});
}
Múltiplos Defers (Ordem LIFO)
const std = @import("std");
pub fn main() void {
std.debug.print("1: início\n", .{});
defer std.debug.print("4: último defer (primeiro a executar)\n", .{});
defer std.debug.print("3: segundo defer\n", .{});
defer std.debug.print("2: primeiro defer (último a executar)\n", .{});
std.debug.print("5: fim do escopo\n", .{});
}
// Saída: 1, 5, 4, 3, 2
Os defers executam em ordem LIFO (Last In, First Out) — como uma pilha. O último defer declarado é o primeiro a executar.
Defer com Arquivo
const std = @import("std");
fn lerArquivo(caminho: []const u8) ![]u8 {
const allocator = std.heap.page_allocator;
const arquivo = try std.fs.cwd().openFile(caminho, .{});
defer arquivo.close(); // Fecha o arquivo ao sair da função
const conteudo = try arquivo.readToEndAlloc(allocator, 1024 * 1024);
return conteudo;
}
Mesmo que readToEndAlloc falhe com um erro, o defer arquivo.close() garante que o descritor de arquivo será fechado.
Defer vs Errdefer
| Característica | defer | errdefer |
|---|---|---|
| Quando executa | Sempre ao sair do escopo | Apenas quando há erro |
| Uso típico | Limpeza geral | Desfazer alocação parcial |
fn criarRecurso(allocator: std.mem.Allocator) !*Recurso {
const ptr = try allocator.create(Recurso);
errdefer allocator.destroy(ptr); // Só libera se o resto falhar
ptr.* = try inicializar(); // Se isso falhar, errdefer executa
return ptr; // Se chegar aqui, errdefer NÃO executa
}
Armadilhas Comuns
- Usar
deferdentro de loop: O defer só executa quando o escopo termina, não a cada iteração. Se precisar de limpeza por iteração, use um bloco explícito. - Esquecer que defers são LIFO: Ao trabalhar com dependências (ex: abrir conexão, depois abrir transação), a ordem dos defers é crucial.
- Captura de variáveis: O
defercaptura ponteiros, não valores. Se a variável for modificada antes do defer executar, o defer verá o valor modificado. - Confundir com
errdefer: Usedeferpara limpeza incondicional eerrdeferpara reverter apenas em caso de erro.
Termos Relacionados
- Errdefer — Defer condicional para erros
- Error Union — Tipo que pode conter erro ou valor
- RAII em Zig — Padrão de gerenciamento de recursos
- Try — Operador de propagação de erros