Arquitetura de Plugins com Zig
Sistemas extensíveis permitem que funcionalidades sejam adicionadas sem recompilar o programa principal. Zig oferece múltiplas estratégias para implementar plugins: bibliotecas dinâmicas, interfaces via comptime e compilação embutida.
Plugins via Interface Comptime
A abordagem mais idiomática em Zig usa comptime para definir interfaces:
const std = @import("std");
/// Interface que todo plugin deve implementar
fn Plugin(comptime T: type) type {
// Verificar em tempo de compilação que T tem os métodos necessários
comptime {
if (!@hasDecl(T, "nome")) @compileError("Plugin deve ter 'nome'");
if (!@hasDecl(T, "inicializar")) @compileError("Plugin deve ter 'inicializar'");
if (!@hasDecl(T, "processar")) @compileError("Plugin deve ter 'processar'");
if (!@hasDecl(T, "finalizar")) @compileError("Plugin deve ter 'finalizar'");
}
return struct {
pub fn executar(entrada: []const u8) ![]const u8 {
try T.inicializar();
defer T.finalizar();
return try T.processar(entrada);
}
};
}
// Plugin de compressão
const CompressaoPlugin = struct {
pub const nome = "compressao";
pub fn inicializar() !void {
std.log.info("Plugin de compressão inicializado", .{});
}
pub fn processar(dados: []const u8) ![]const u8 {
_ = dados;
return "dados_comprimidos";
}
pub fn finalizar() void {
std.log.info("Plugin de compressão finalizado", .{});
}
};
// Verificação em tempo de compilação
const compressao = Plugin(CompressaoPlugin);
test "plugin de compressão" {
const resultado = try compressao.executar("teste");
try std.testing.expectEqualStrings("dados_comprimidos", resultado);
}
Plugins via Shared Libraries
Para plugins carregados em runtime:
const std = @import("std");
const PluginAPI = struct {
nome: [*:0]const u8,
inicializar: *const fn () callconv(.C) c_int,
processar: *const fn ([*]const u8, usize) callconv(.C) [*]const u8,
finalizar: *const fn () callconv(.C) void,
};
const PluginLoader = struct {
plugins: std.ArrayList(LoadedPlugin),
const LoadedPlugin = struct {
handle: ?*anyopaque,
api: PluginAPI,
};
pub fn init(allocator: std.mem.Allocator) PluginLoader {
return .{ .plugins = std.ArrayList(LoadedPlugin).init(allocator) };
}
pub fn carregar(self: *PluginLoader, caminho: [*:0]const u8) !void {
const handle = std.DynLib.open(caminho) catch return error.PluginNaoEncontrado;
const get_api = handle.lookup(
*const fn () callconv(.C) PluginAPI,
"plugin_get_api",
) orelse return error.APINaoEncontrada;
const api = get_api();
try self.plugins.append(.{ .handle = handle.handle, .api = api });
_ = api.inicializar();
}
};
Plugin como Shared Library
// meu_plugin.zig — compilado como .so/.dll
const api = PluginAPI{
.nome = "meu_plugin",
.inicializar = inicializar,
.processar = processar,
.finalizar = finalizar,
};
export fn plugin_get_api() callconv(.C) PluginAPI {
return api;
}
fn inicializar() callconv(.C) c_int {
return 0; // Sucesso
}
fn processar(dados: [*]const u8, len: usize) callconv(.C) [*]const u8 {
_ = dados;
_ = len;
return "processado";
}
fn finalizar() callconv(.C) void {}
Registro de Plugins com Comptime
fn PluginRegistry(comptime plugins: []const type) type {
return struct {
pub fn executarTodos(entrada: []const u8) !void {
inline for (plugins) |P| {
try P.inicializar();
_ = try P.processar(entrada);
P.finalizar();
}
}
pub fn buscarPorNome(comptime nome: []const u8) ?type {
inline for (plugins) |P| {
if (std.mem.eql(u8, P.nome, nome)) return P;
}
return null;
}
};
}
Conclusão
Zig oferece estratégias complementares para plugins: interfaces comptime para plugins compilados junto com a aplicação (zero overhead, verificação em tempo de compilação) e shared libraries para plugins carregados em runtime. A escolha depende se você precisa de extensibilidade em tempo de compilação ou em runtime.
Conteúdo Relacionado
- Comptime em Zig — Metaprogramação
- Design Patterns em Zig — Padrões de projeto
- Interoperabilidade Zig-C — Shared libs
- Clean Code em Zig — Boas práticas
- Zig Build System — Compilar plugins