RAII em Zig — O que é e Como Usar

RAII em Zig — O que é e Como Usar

Definição

RAII (Resource Acquisition Is Initialization) é um padrão de programação onde a aquisição de um recurso está vinculada à sua liberação. Em C++ e Rust, isso é feito com destructors automáticos. Em Zig, o padrão RAII é implementado explicitamente usando defer e errdefer — o programador declara a limpeza imediatamente após a aquisição, e o compilador garante que ela será executada ao sair do escopo.

Zig deliberadamente evita destructors implícitos para manter o código explícito e previsível. A filosofia é: “se algo importante está acontecendo, deve ser visível no código.”

Por que RAII em Zig Importa

  1. Sem vazamentos: defer garante liberação mesmo em caminhos de erro.
  2. Explícito: A limpeza é visível no código, não escondida em destructors.
  3. Flexível: errdefer libera apenas quando há erro — ideal para inicialização parcial.
  4. Composável: Múltiplos defer executam em ordem reversa (LIFO).

Exemplo Prático

Padrão defer para Limpeza

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit(); // Liberado ao final da função

    const allocator = gpa.allocator();

    // Alocação + defer para liberação (RAII)
    const buffer = try allocator.alloc(u8, 1024);
    defer allocator.free(buffer);

    // Abrir arquivo + defer para fechar
    const arquivo = try std.fs.cwd().openFile("dados.txt", .{});
    defer arquivo.close();

    // Usar buffer e arquivo normalmente...
    const bytes_lidos = try arquivo.read(buffer);
    std.debug.print("Lidos: {} bytes\n", .{bytes_lidos});

    // Ao sair do escopo (normal ou erro), defers executam em ordem reversa:
    // 1. arquivo.close()
    // 2. allocator.free(buffer)
    // 3. gpa.deinit()
}

errdefer para Inicialização Parcial

const std = @import("std");

const Servidor = struct {
    socket: std.posix.socket_t,
    buffer: []u8,
    allocator: std.mem.Allocator,

    pub fn init(allocator: std.mem.Allocator) !Servidor {
        // Alocar buffer
        const buffer = try allocator.alloc(u8, 4096);
        errdefer allocator.free(buffer); // Libera se o próximo passo falhar

        // Criar socket (pode falhar)
        const socket = try std.posix.socket(
            std.posix.AF.INET,
            std.posix.SOCK.STREAM,
            0,
        );
        errdefer std.posix.close(socket); // Fecha se os próximos passos falharem

        return Servidor{
            .socket = socket,
            .buffer = buffer,
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *Servidor) void {
        std.posix.close(self.socket);
        self.allocator.free(self.buffer);
    }
};

Padrão init/deinit (RAII Idiomático em Zig)

const std = @import("std");

fn Lista(comptime T: type) type {
    return struct {
        items: std.ArrayList(T),

        const Self = @This();

        pub fn init(allocator: std.mem.Allocator) Self {
            return .{
                .items = std.ArrayList(T).init(allocator),
            };
        }

        pub fn deinit(self: *Self) void {
            self.items.deinit();
        }

        pub fn adicionar(self: *Self, valor: T) !void {
            try self.items.append(valor);
        }
    };
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var lista = Lista(u32).init(gpa.allocator());
    defer lista.deinit(); // RAII: limpeza garantida

    try lista.adicionar(10);
    try lista.adicionar(20);
}

RAII em Zig vs C++ vs Rust

AspectoC++RustZig
MecanismoDestructor (~Class)Drop traitdefer/errdefer
ImplícitoSimSimNunca
Visível no códigoParcialmenteParcialmenteSempre
OrdemReversaReversaReversa
CondicionalNãoNãoSim (errdefer)

Armadilhas Comuns

  • Esquecer o defer: Sem destructors automáticos, esquecer defer deinit() causa leak. O GPA detecta isso em modo debug.
  • defer em loops: defer dentro de um loop executa ao fim da iteração, não ao fim do loop. Isso pode causar uso excessivo de memória se defers se acumularem.
  • Ordem de defer: Defers executam em ordem reversa (LIFO). A ordem importa quando recursos dependem uns dos outros.
  • errdefer vs defer: Use errdefer durante inicialização (antes de retornar com sucesso) e defer no chamador para limpeza normal.

Termos Relacionados

  • Defer — Mecanismo de limpeza ao sair do escopo
  • Errdefer — Limpeza condicional em caso de erro
  • Allocator — Gerenciamento de memória
  • Memory Leak — O que acontece sem RAII

Tutoriais Relacionados

Continue aprendendo Zig

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