Vtable Interface em Zig — O que é e Como Usar
Definição
Uma vtable (virtual table) em Zig é uma struct de ponteiros de função usada para implementar polimorfismo em runtime. Como Zig não tem herança, traits ou interfaces na linguagem, vtables são o padrão idiomático para quando você precisa de dispatch dinâmico — ou seja, chamar métodos diferentes dependendo do tipo concreto, sem conhecer o tipo em tempo de compilação.
A biblioteca padrão do Zig usa vtables extensivamente. O tipo std.mem.Allocator é o exemplo mais conhecido: ele armazena um ponteiro para implementação e uma vtable com as funções alloc, resize e free.
Por que Vtable Interface Importa
- Polimorfismo em runtime: Armazenar diferentes tipos em uma mesma variável.
- Extensibilidade: Novos tipos podem implementar a interface sem modificar código existente.
- Padrão da std lib: Allocator, Writer e Reader da std usam vtables.
- Controle total: O programador controla o layout de memória e o overhead.
Exemplo Prático
Interface Forma com Vtable
const std = @import("std");
const Forma = struct {
ptr: *anyopaque,
vtable: *const VTable,
const VTable = struct {
areaFn: *const fn (ptr: *anyopaque) f64,
nomeFn: *const fn (ptr: *anyopaque) []const u8,
};
pub fn area(self: Forma) f64 {
return self.vtable.areaFn(self.ptr);
}
pub fn nome(self: Forma) []const u8 {
return self.vtable.nomeFn(self.ptr);
}
pub fn init(ptr: anytype) Forma {
const T = @TypeOf(ptr);
const PtrInfo = @typeInfo(T);
const Child = PtrInfo.pointer.child;
return .{
.ptr = @ptrCast(ptr),
.vtable = &.{
.areaFn = @ptrCast(&struct {
pub fn call(p: *anyopaque) f64 {
const self: *Child = @ptrCast(@alignCast(p));
return self.area();
}
}.call),
.nomeFn = @ptrCast(&struct {
pub fn call(p: *anyopaque) []const u8 {
const self: *Child = @ptrCast(@alignCast(p));
return self.nome();
}
}.call),
},
};
}
};
Implementando a Interface
const std = @import("std");
const Circulo = struct {
raio: f64,
pub fn area(self: *Circulo) f64 {
return std.math.pi * self.raio * self.raio;
}
pub fn nome(_: *Circulo) []const u8 {
return "Círculo";
}
};
const Retangulo = struct {
largura: f64,
altura: f64,
pub fn area(self: *Retangulo) f64 {
return self.largura * self.altura;
}
pub fn nome(_: *Retangulo) []const u8 {
return "Retângulo";
}
};
pub fn main() void {
var circ = Circulo{ .raio = 5.0 };
var ret = Retangulo{ .largura = 4.0, .altura = 6.0 };
// Ambos podem ser tratados como Forma
const formas = [_]Forma{
Forma.init(&circ),
Forma.init(&ret),
};
for (formas) |forma| {
std.debug.print("{s}: área = {d:.2}\n", .{ forma.nome(), forma.area() });
}
}
Padrão da std lib: Allocator
const std = @import("std");
// std.mem.Allocator é uma vtable interface!
// Qualquer allocator pode ser usado uniformemente:
fn alocarEUsar(allocator: std.mem.Allocator) !void {
const dados = try allocator.alloc(u8, 100);
defer allocator.free(dados);
@memset(dados, 'Z');
std.debug.print("Primeiros bytes: {s}\n", .{dados[0..5]});
}
pub fn main() !void {
// Mesma função, diferentes implementações
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
try alocarEUsar(gpa.allocator()); // GPA
try alocarEUsar(std.heap.page_allocator); // Page allocator
}
Quando Usar Vtable vs anytype
| Critério | anytype (comptime) | Vtable (runtime) |
|---|---|---|
| Tipo conhecido em comptime | Sim | Nunca |
| Armazenar em coleção | Apenas mesmo tipo | Tipos diferentes |
| Performance | Inlining possível | Indireção por ponteiro |
| Tamanho do binário | Maior (duplicação) | Menor |
| Uso típico | Funções genéricas | Plugins, coleções heterogêneas |
Armadilhas Comuns
- Lifetime: O ponteiro na vtable deve apontar para dados que sobrevivem ao uso da interface. Cuidado com variáveis locais.
- Alinhamento:
@ptrCaste@alignCastsão necessários ao converter*anyopaquede volta para o tipo concreto. - Overhead: Cada chamada via vtable tem overhead de indireção. Para código hot-path, prefira
anytype(comptime). - Complexidade: Vtables manuais são verbosas. Avalie se
anytypeou umunion(enum)seriam mais simples.
Termos Relacionados
- anytype — Alternativa comptime a vtables
- Allocator — Exemplo clássico de vtable em Zig
- Tagged Union — Alternativa com tipos fechados
- Writer Interface — Interface que usa vtable internamente
- Struct — Structs armazenam a vtable