Defer em Zig — O que é e Como Usar

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

  1. Gerenciamento de recursos: Garante que memória, arquivos e conexões sejam liberados.
  2. Código mais limpo: A alocação e a desalocação ficam próximas no código, facilitando a leitura.
  3. Segurança contra erros: Mesmo que uma função retorne um erro com try, o defer ainda executa.
  4. Substituição do RAII: Em Zig não há destrutores automáticos; defer cumpre 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ísticadefererrdefer
Quando executaSempre ao sair do escopoApenas quando há erro
Uso típicoLimpeza geralDesfazer 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 defer dentro 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 defer captura ponteiros, não valores. Se a variável for modificada antes do defer executar, o defer verá o valor modificado.
  • Confundir com errdefer: Use defer para limpeza incondicional e errdefer para 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

Tutoriais Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.