Esquecer defer — Como Resolver em Zig

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çãodefer 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

Continue aprendendo Zig

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