Perguntas de Entrevista sobre Design Patterns em Zig
Design patterns em Zig diferem significativamente dos padrões clássicos de OOP. Zig favorece composição sobre herança, explicitação sobre magia, e simplicidade sobre sofisticação. Entrevistadores de posições seniores avaliam sua capacidade de projetar APIs e arquiteturas idiomáticas em Zig.
Padrões Idiomáticos de Zig
Explique o Allocator Pattern.
O padrão mais fundamental de Zig: funções que alocam memória recebem um Allocator como parâmetro, tornando alocação explícita e testável. Veja perguntas de memória para detalhes aprofundados.
const Parser = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) Parser {
return .{ .allocator = allocator };
}
pub fn parse(self: *Parser, input: []const u8) !Ast {
const nodes = try self.allocator.alloc(Node, 100);
errdefer self.allocator.free(nodes);
// ...
}
};
Como Zig implementa interfaces/polimorfismo?
Zig não tem interfaces ou classes abstratas. O polimorfismo é alcançado via:
1. Comptime duck typing (mais idiomático):
fn processar(writer: anytype) !void {
try writer.writeAll("dados");
}
// Aceita qualquer tipo que tenha .writeAll
2. Fat pointers (vtable manual):
const Writer = struct {
ptr: *anyopaque,
writeFn: *const fn (*anyopaque, []const u8) error{...}!usize,
pub fn write(self: Writer, data: []const u8) !usize {
return self.writeFn(self.ptr, data);
}
};
A biblioteca padrão usa esse padrão extensivamente (ex: std.mem.Allocator).
Explique o padrão Init/Deinit.
O equivalente de Zig ao RAII de C++ — explícito e sem magia:
const Recurso = struct {
dados: []u8,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) !Recurso {
return .{
.dados = try allocator.alloc(u8, 1024),
.allocator = allocator,
};
}
pub fn deinit(self: *Recurso) void {
self.allocator.free(self.dados);
}
};
// Uso:
var r = try Recurso.init(allocator);
defer r.deinit();
O que é o padrão “return error or value” (Builder pattern para erros)?
fn configurar() !Config {
var config = Config{};
config.host = try resolverHost();
errdefer config.host.deinit();
config.porta = try lerPorta();
config.tls = try configurarTLS();
errdefer config.tls.deinit();
return config;
}
Cada passo pode falhar, e errdefer garante cleanup de todos os passos anteriores.
Padrões de Composição
Como compor funcionalidade sem herança?
Zig usa composição de structs e embeddings:
const Logger = struct {
pub fn log(self: *Logger, msg: []const u8) void { ... }
};
const HttpServer = struct {
logger: Logger,
config: Config,
pub fn handleRequest(self: *HttpServer, req: Request) !Response {
self.logger.log("Recebida requisição");
// ...
}
};
Explique o padrão Sentinel.
Zig usa sentinels para marcar o fim de sequências — similar a null-terminated strings em C, mas generalizado:
// Slice com sentinel 0
const str: [:0]const u8 = "hello"; // null-terminated
const arr: [5:0]u8 = .{ 1, 2, 3, 4, 5 }; // sentinel 0 após o último
Sentinels são verificados em tempo de compilação, eliminando classes de bugs de C.
Preparação
Design patterns são testados em posições seniores. Estude o código-fonte da biblioteca padrão de Zig para exemplos de bons padrões. Combine com perguntas de error handling, memória e comptime. Explore o ecossistema para ver padrões em projetos reais e pratique com desafios.