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
- Sem vazamentos:
defergarante liberação mesmo em caminhos de erro. - Explícito: A limpeza é visível no código, não escondida em destructors.
- Flexível:
errdeferlibera apenas quando há erro — ideal para inicialização parcial. - Composável: Múltiplos
deferexecutam 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
| Aspecto | C++ | Rust | Zig |
|---|---|---|---|
| Mecanismo | Destructor (~Class) | Drop trait | defer/errdefer |
| Implícito | Sim | Sim | Nunca |
| Visível no código | Parcialmente | Parcialmente | Sempre |
| Ordem | Reversa | Reversa | Reversa |
| Condicional | Não | Não | Sim (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:
deferdentro 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
errdeferdurante inicialização (antes de retornar com sucesso) edeferno 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