---
title: "Vtable Interface em Zig — O que é e Como Usar"
url: "https://ziglang.com.br/glossario/vtable-interface-em-zig-o-que-%C3%A9-e-como-usar/"
markdown_url: "https://ziglang.com.br/glossario/vtable-interface-em-zig-o-que-%C3%A9-e-como-usar.MD"
description: "Entenda vtables e dispatch virtual em Zig: polimorfismo em runtime usando ponteiros de função. Alternativa a herança e traits. Guia pt-BR."
date: "2026-02-21"
author: "Zig Brasil"
---

# Vtable Interface em Zig — O que é e Como Usar

Entenda vtables e dispatch virtual em Zig: polimorfismo em runtime usando ponteiros de função. Alternativa a herança e traits. Guia pt-BR.


# 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

1. **Polimorfismo em runtime**: Armazenar diferentes tipos em uma mesma variável.
2. **Extensibilidade**: Novos tipos podem implementar a interface sem modificar código existente.
3. **Padrão da std lib**: Allocator, Writer e Reader da std usam vtables.
4. **Controle total**: O programador controla o layout de memória e o overhead.

## Exemplo Prático

### Interface Forma com Vtable

```zig
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

```zig
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

```zig
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 |

## Alternativa: Tagged Union

Para um conjunto **fechado** de tipos conhecidos em tempo de compilação, um `union(enum)` pode ser mais simples e eficiente que uma vtable:

```zig
const Forma = union(enum) {
    circulo: struct { raio: f64 },
    retangulo: struct { largura: f64, altura: f64 },

    pub fn area(self: Forma) f64 {
        return switch (self) {
            .circulo => |c| std.math.pi * c.raio * c.raio,
            .retangulo => |r| r.largura * r.altura,
        };
    }
};
```

**Use vtable quando**: novos tipos podem ser adicionados pelo usuário sem modificar a biblioteca (plugins, extensões, allocators).
**Use tagged union quando**: o conjunto de tipos é fixo e conhecido na compilação.

## Boas Práticas

- **Imite o padrão `Allocator` da std**: A convenção `ptr: *anyopaque` + `vtable: *const VTable` é bem estabelecida. Seguir esse padrão torna o código reconhecível para programadores Zig.
- **Mantenha a vtable em memória estática**: Vtables geralmente são `const` e vivem no segmento de dados. Declare-as com `const vtable = VTable{ ... }` em escopo de namespace, não como variável local.
- **Documente os invariantes de lifetime**: O ponteiro `ptr` não gerencia a vida do objeto concreto. Documente que o chamador é responsável por garantir que o objeto viva enquanto a interface for usada.
- **Considere `union(enum)` antes de vtable**: Vtables têm overhead de indireção e código mais verboso. Use-as apenas quando o polimorfismo em runtime com tipos abertos for realmente necessário.

## Armadilhas Comuns

- **Lifetime**: O ponteiro na vtable deve apontar para dados que sobrevivem ao uso da interface. Cuidado com variáveis locais.
- **Alinhamento**: `@ptrCast` e `@alignCast` são necessários ao converter `*anyopaque` de 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 `anytype` ou um `union(enum)` seriam mais simples.

## Termos Relacionados

- [anytype](/glossario/anytype/) — Alternativa comptime a vtables
- [Allocator](/glossario/allocator/) — Exemplo clássico de vtable em Zig
- [Tagged Union](/glossario/tagged-union/) — Alternativa com tipos fechados
- [Writer Interface](/glossario/writer-interface/) — Interface que usa vtable internamente
- [Struct](/glossario/struct/) — Structs armazenam a vtable

## Tutoriais Relacionados

- [Zig Design Patterns](/tutoriais/zig-design-patterns/)
- [Gerenciamento de Memória em Zig](/tutoriais/gerenciamento-de-memoria-zig/)
- [Introdução ao Zig](/tutoriais/introducao-ao-zig/)
