Cheatsheet: Builder em Zig

Builder em Zig

O padrão Builder separa a construção de um objeto complexo de sua representação, permitindo criar diferentes configurações passo a passo. Em Zig, este padrão é extremamente natural graças aos valores padrão em structs e à convenção de Options structs.

Quando Usar

  • Objetos com muitos parâmetros opcionais
  • Configuração de servidores, clientes e conexões
  • Construção de queries, comandos ou requisições
  • Quando a ordem de configuração importa ou requer validação

Padrão Idiomático: Options Struct

A abordagem mais idiomática em Zig para o Builder é o Options struct com valores padrão:

const std = @import("std");

const Servidor = struct {
    host: []const u8,
    porta: u16,
    max_conexoes: u32,
    timeout_ms: u64,
    tls: bool,
    workers: u16,

    const Options = struct {
        host: []const u8 = "0.0.0.0",
        porta: u16 = 8080,
        max_conexoes: u32 = 1024,
        timeout_ms: u64 = 30_000,
        tls: bool = false,
        workers: u16 = 4,
    };

    pub fn init(opcoes: Options) Servidor {
        return .{
            .host = opcoes.host,
            .porta = opcoes.porta,
            .max_conexoes = opcoes.max_conexoes,
            .timeout_ms = opcoes.timeout_ms,
            .tls = opcoes.tls,
            .workers = opcoes.workers,
        };
    }

    pub fn iniciar(self: *const Servidor) !void {
        std.debug.print("Servidor {s}:{d} ({d} workers, TLS: {})\n", .{
            self.host, self.porta, self.workers, self.tls,
        });
    }
};

pub fn main() !void {
    // Usar valores padrão
    const srv1 = Servidor.init(.{});
    try srv1.iniciar();

    // Customizar apenas o necessário
    const srv2 = Servidor.init(.{
        .porta = 443,
        .tls = true,
        .workers = 8,
    });
    try srv2.iniciar();
}

Builder com API Fluente (Method Chaining)

const std = @import("std");

const QueryBuilder = struct {
    tabela: []const u8 = "",
    campos: [10][]const u8 = undefined,
    num_campos: usize = 0,
    condicao: ?[]const u8 = null,
    limite: ?u32 = null,
    ordenar_por: ?[]const u8 = null,

    pub fn select(campos: []const []const u8) QueryBuilder {
        var qb = QueryBuilder{};
        for (campos, 0..) |campo, i| {
            if (i >= 10) break;
            qb.campos[i] = campo;
            qb.num_campos = i + 1;
        }
        return qb;
    }

    pub fn from(self: QueryBuilder, tabela: []const u8) QueryBuilder {
        var qb = self;
        qb.tabela = tabela;
        return qb;
    }

    pub fn where(self: QueryBuilder, condicao: []const u8) QueryBuilder {
        var qb = self;
        qb.condicao = condicao;
        return qb;
    }

    pub fn limit(self: QueryBuilder, n: u32) QueryBuilder {
        var qb = self;
        qb.limite = n;
        return qb;
    }

    pub fn orderBy(self: QueryBuilder, campo: []const u8) QueryBuilder {
        var qb = self;
        qb.ordenar_por = campo;
        return qb;
    }

    pub fn build(self: *const QueryBuilder) !void {
        const stdout = std.io.getStdOut().writer();
        try stdout.writeAll("SELECT ");
        for (self.campos[0..self.num_campos], 0..) |campo, i| {
            if (i > 0) try stdout.writeAll(", ");
            try stdout.writeAll(campo);
        }
        try stdout.print(" FROM {s}", .{self.tabela});
        if (self.condicao) |c| try stdout.print(" WHERE {s}", .{c});
        if (self.ordenar_por) |o| try stdout.print(" ORDER BY {s}", .{o});
        if (self.limite) |l| try stdout.print(" LIMIT {d}", .{l});
        try stdout.writeAll(";\n");
    }
};

pub fn main() !void {
    const query = QueryBuilder
        .select(&.{ "nome", "email", "idade" })
        .from("usuarios")
        .where("idade > 18")
        .orderBy("nome")
        .limit(10);

    try query.build();
    // SELECT nome, email, idade FROM usuarios WHERE idade > 18 ORDER BY nome LIMIT 10;
}

Builder com Validação

const std = @import("std");

const ConfigDB = struct {
    host: []const u8,
    porta: u16,
    database: []const u8,
    usuario: []const u8,
    senha: []const u8,
    pool_size: u16,

    const BuildError = error{
        HostObrigatorio,
        DatabaseObrigatoria,
        UsuarioObrigatorio,
        PortaInvalida,
    };

    const Builder = struct {
        host: ?[]const u8 = null,
        porta: u16 = 5432,
        database: ?[]const u8 = null,
        usuario: ?[]const u8 = null,
        senha: []const u8 = "",
        pool_size: u16 = 10,

        pub fn setHost(self: Builder, host: []const u8) Builder {
            var b = self;
            b.host = host;
            return b;
        }

        pub fn setPorta(self: Builder, porta: u16) Builder {
            var b = self;
            b.porta = porta;
            return b;
        }

        pub fn setDatabase(self: Builder, db: []const u8) Builder {
            var b = self;
            b.database = db;
            return b;
        }

        pub fn setUsuario(self: Builder, usuario: []const u8) Builder {
            var b = self;
            b.usuario = usuario;
            return b;
        }

        pub fn setSenha(self: Builder, senha: []const u8) Builder {
            var b = self;
            b.senha = senha;
            return b;
        }

        pub fn build(self: *const Builder) BuildError!ConfigDB {
            return .{
                .host = self.host orelse return error.HostObrigatorio,
                .porta = self.porta,
                .database = self.database orelse return error.DatabaseObrigatoria,
                .usuario = self.usuario orelse return error.UsuarioObrigatorio,
                .senha = self.senha,
                .pool_size = self.pool_size,
            };
        }
    };
};

pub fn main() !void {
    const config = try (ConfigDB.Builder{})
        .setHost("db.exemplo.com")
        .setDatabase("meu_app")
        .setUsuario("admin")
        .setSenha("seguro123")
        .build();

    std.debug.print("Conectando a {s}:{d}/{s}\n", .{
        config.host, config.porta, config.database,
    });
}

Quando Evitar

  • Structs simples com poucos campos — use inicialização direta .{ .campo = valor }
  • Quando o Options struct resolve o problema de forma mais simples
  • Objetos imutáveis que não precisam de construção incremental

Veja Também

  • Factory — Criação de objetos por tipo
  • Structs — Valores padrão e inicialização
  • Command — Encapsular operações como objetos
  • Pipeline — Processamento em estágios
  • Receitas — Exemplos práticos de builders

Continue aprendendo Zig

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