Singleton em Zig
O padrão Singleton garante que uma classe/struct tenha apenas uma instância durante toda a execução do programa, fornecendo um ponto de acesso global a ela. Em Zig, esse padrão é implementado de forma diferente de linguagens OOP tradicionais, utilizando variáveis globais, std.once e comptime.
Quando Usar
- Gerenciador de configuração global
- Pool de conexões com banco de dados
- Sistema de logging centralizado
- Cache global da aplicação
- Acesso a hardware (ex: GPIO em sistemas embarcados)
Implementação Básica
Singleton com variável global
const std = @import("std");
const Logger = struct {
nivel: NivelLog,
arquivo: ?std.fs.File,
const NivelLog = enum { debug, info, warn, err };
// Instância global única
var instancia: ?Logger = null;
var mutex = std.Thread.Mutex{};
pub fn obterInstancia() *Logger {
mutex.lock();
defer mutex.unlock();
if (instancia == null) {
instancia = Logger{
.nivel = .info,
.arquivo = null,
};
}
return &instancia.?;
}
pub fn log(self: *Logger, nivel: NivelLog, mensagem: []const u8) void {
if (@intFromEnum(nivel) < @intFromEnum(self.nivel)) return;
std.debug.print("[{s}] {s}\n", .{ @tagName(nivel), mensagem });
}
};
pub fn main() void {
const logger = Logger.obterInstancia();
logger.log(.info, "Aplicação iniciada");
logger.log(.warn, "Recurso quase esgotado");
logger.log(.debug, "Esta mensagem não aparece (nível < info)");
// Em qualquer parte do código, mesma instância
const mesmo_logger = Logger.obterInstancia();
mesmo_logger.log(.err, "Erro crítico!");
}
Singleton com std.once (thread-safe garantido)
const std = @import("std");
const Config = struct {
porta: u16,
host: []const u8,
max_conexoes: u32,
var instancia: Config = undefined;
var once = std.once(inicializar);
fn inicializar() void {
instancia = Config{
.porta = 8080,
.host = "localhost",
.max_conexoes = 100,
};
}
pub fn obter() *Config {
once.call(); // executado apenas uma vez, thread-safe
return &instancia;
}
};
pub fn main() void {
const config = Config.obter();
std.debug.print("Servidor: {s}:{d}\n", .{ config.host, config.porta });
// Sempre retorna a mesma instância
const config2 = Config.obter();
std.debug.print("Max conexões: {d}\n", .{config2.max_conexoes});
}
Singleton Genérico com comptime
fn Singleton(comptime T: type, comptime initFn: fn () T) type {
return struct {
var instancia: ?T = null;
var mutex = std.Thread.Mutex{};
pub fn obter() *T {
mutex.lock();
defer mutex.unlock();
if (instancia == null) {
instancia = initFn();
}
return &instancia.?;
}
pub fn resetar() void {
mutex.lock();
defer mutex.unlock();
instancia = null;
}
};
}
// Uso:
const MeuSingleton = Singleton(MinhaStruct, MinhaStruct.criar);
const inst = MeuSingleton.obter();
Quando Evitar
- Testes unitários: Singletons criam estado global que dificulta testes isolados. Prefira Dependency Injection quando testabilidade é prioridade.
- Acoplamento excessivo: Se muitas partes do código dependem do singleton, considere passar a dependência explicitamente.
- Concorrência pesada: Para dados compartilhados entre muitas threads, considere um Pool de Objetos ou dados por thread.
Alternativa Idiomática em Zig
Em Zig, muitas vezes é preferível simplesmente passar a dependência como parâmetro, seguindo a filosofia de tornar tudo explícito:
const Logger = struct {
nivel: NivelLog,
// ...
};
fn processarRequisicao(logger: *Logger, dados: []const u8) !void {
logger.log(.info, "Processando requisição");
// ...
}
Veja Também
- Dependency Injection — Alternativa ao Singleton para testabilidade
- Factory — Criação controlada de objetos
- Concorrência — Thread safety em Zig
- FAQ Produção — Padrões para código em produção