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
- Enums e Unions — Enumerações e tagged unions
- Funções — Métodos e funções genéricas
- Comptime — Generics e metaprogramação
- Padrão Builder — Padrão builder em detalhes
- Padrão Factory — Criação de objetos
- Type Erasure — Interfaces dinâmicas