Arquitetura de Plugins com Zig

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

Continue aprendendo Zig

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