Cheatsheet: Structs em Zig

Cheatsheet: Structs em Zig

Structs são o principal mecanismo de composição de dados em Zig. Diferente de linguagens OOP tradicionais, Zig não tem herança, mas oferece composição poderosa com generics e comptime.

Definição Básica

const Ponto = struct {
    x: f64,
    y: f64,
};

// Criação
const p = Ponto{ .x = 1.0, .y = 2.0 };

// Acesso
const x = p.x;  // 1.0
const y = p.y;  // 2.0

Valores Padrão

const Config = struct {
    porta: u16 = 8080,
    host: []const u8 = "localhost",
    max_conexoes: u32 = 100,
    debug: bool = false,
};

// Usar valores padrão
const cfg1 = Config{};  // tudo padrão
const cfg2 = Config{ .porta = 3000, .debug = true };  // parcial

Métodos

const Vetor2D = struct {
    x: f64,
    y: f64,

    const Self = @This();

    // Método de instância (imutável)
    pub fn magnitude(self: Self) f64 {
        return @sqrt(self.x * self.x + self.y * self.y);
    }

    // Método mutável
    pub fn normalizar(self: *Self) void {
        const mag = self.magnitude();
        if (mag > 0) {
            self.x /= mag;
            self.y /= mag;
        }
    }

    // Método "estático" (sem self)
    pub fn zero() Self {
        return .{ .x = 0, .y = 0 };
    }

    // Método com retorno de nova instância
    pub fn adicionar(self: Self, outro: Self) Self {
        return .{
            .x = self.x + outro.x,
            .y = self.y + outro.y,
        };
    }

    // Formatação customizada
    pub fn format(
        self: Self,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        _ = fmt;
        _ = options;
        try writer.print("({d:.2}, {d:.2})", .{ self.x, self.y });
    }
};

// Uso
var v = Vetor2D{ .x = 3.0, .y = 4.0 };
const mag = v.magnitude();  // 5.0
v.normalizar();
const soma = v.adicionar(Vetor2D{ .x = 1.0, .y = 0.0 });
_ = mag;
_ = soma;

Structs Genéricas

fn Stack(comptime T: type) type {
    return struct {
        items: []T,
        count: usize,
        allocator: std.mem.Allocator,

        const Self = @This();

        pub fn init(allocator: std.mem.Allocator) Self {
            return .{
                .items = &[_]T{},
                .count = 0,
                .allocator = allocator,
            };
        }

        pub fn deinit(self: *Self) void {
            if (self.items.len > 0) {
                self.allocator.free(self.items);
            }
        }

        pub fn push(self: *Self, item: T) !void {
            if (self.count >= self.items.len) {
                const new_cap = if (self.items.len == 0) 8 else self.items.len * 2;
                self.items = try self.allocator.realloc(self.items, new_cap);
            }
            self.items[self.count] = item;
            self.count += 1;
        }

        pub fn pop(self: *Self) ?T {
            if (self.count == 0) return null;
            self.count -= 1;
            return self.items[self.count];
        }
    };
}

// Uso
var stack = Stack(i32).init(allocator);
defer stack.deinit();
try stack.push(42);

Packed Structs

// Packed struct — layout de memória exato, sem padding
const Flags = packed struct {
    leitura: bool,    // 1 bit
    escrita: bool,    // 1 bit
    execucao: bool,   // 1 bit
    _reservado: u5,   // 5 bits
};

// Total: exatamente 1 byte (8 bits)
const flags = Flags{
    .leitura = true,
    .escrita = true,
    .execucao = false,
    ._reservado = 0,
};

// Converter para/de inteiro
const como_byte: u8 = @bitCast(flags);
const de_byte: Flags = @bitCast(@as(u8, 0b00000011));
_ = como_byte;
_ = de_byte;

// Packed struct com campos maiores
const Header = packed struct {
    version: u4,
    tipo: u4,
    tamanho: u16,
    checksum: u8,
};
// Total: exatamente 4 bytes

Extern Structs

// Extern struct — compatível com layout C
const CStruct = extern struct {
    x: c_int,
    y: c_int,
    nome: [*:0]const u8,
};

// Útil para FFI com C
extern fn c_function(s: *CStruct) void;

Struct Anônima (Tupla)

// Tupla — struct sem nomes de campos
const tupla = .{ 42, "hello", true };

// Acesso por índice comptime
const primeiro = tupla[0];  // 42
const segundo = tupla[1];   // "hello"

// Tipo explícito
const par: struct { i32, []const u8 } = .{ 42, "hello" };

// Função retornando tupla
fn dividir(a: i32, b: i32) struct { i32, i32 } {
    return .{ @divTrunc(a, b), @mod(a, b) };
}

Composição (em vez de Herança)

const Logger = struct {
    pub fn log(self: *Logger, msg: []const u8) void {
        _ = self;
        std.debug.print("LOG: {s}\n", .{msg});
    }
};

const Servidor = struct {
    porta: u16,
    logger: Logger,

    pub fn init(porta: u16) Servidor {
        return .{
            .porta = porta,
            .logger = Logger{},
        };
    }

    pub fn iniciar(self: *Servidor) void {
        self.logger.log("Servidor iniciando...");
        // ... lógica do servidor
    }
};

Interfaces via Ponteiros de Função

const Writer = struct {
    ptr: *anyopaque,
    writeFn: *const fn (*anyopaque, []const u8) anyerror!usize,

    pub fn write(self: Writer, data: []const u8) !usize {
        return self.writeFn(self.ptr, data);
    }
};

const MeuWriter = struct {
    buffer: []u8,
    pos: usize,

    pub fn writer(self: *MeuWriter) Writer {
        return .{
            .ptr = @ptrCast(self),
            .writeFn = @ptrCast(&write),
        };
    }

    fn write(self: *MeuWriter, data: []const u8) !usize {
        const espaco = self.buffer.len - self.pos;
        const n = @min(data.len, espaco);
        @memcpy(self.buffer[self.pos..][0..n], data[0..n]);
        self.pos += n;
        return n;
    }
};

Reflexão em Structs

fn imprimirCampos(comptime T: type) void {
    const info = @typeInfo(T);
    switch (info) {
        .@"struct" => |s| {
            inline for (s.fields) |field| {
                std.debug.print("Campo: {s}, Tipo: {s}\n", .{
                    field.name,
                    @typeName(field.type),
                });
            }
        },
        else => @compileError("Esperado struct"),
    }
}

// Uso
imprimirCampos(Ponto);
// Saída:
// Campo: x, Tipo: f64
// Campo: y, Tipo: f64

Padrões Comuns

// Padrão init/deinit
const Resource = struct {
    data: []u8,
    allocator: std.mem.Allocator,

    pub fn init(allocator: std.mem.Allocator, size: usize) !Resource {
        return .{
            .data = try allocator.alloc(u8, size),
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *Resource) void {
        self.allocator.free(self.data);
        self.* = undefined;  // envenenar dados
    }
};

// Uso com defer
var res = try Resource.init(allocator, 1024);
defer res.deinit();

// Padrão Builder
const QueryBuilder = struct {
    tabela: []const u8 = "",
    condicao: []const u8 = "",
    limite: ?u32 = null,

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

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

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

const query = QueryBuilder.from("usuarios")
    .where("idade > 18")
    .limit(10);
_ = query;

Veja Também

Continue aprendendo Zig

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