---
title: "Cheatsheet: Type Erasure em Zig"
url: "https://ziglang.com.br/padroes/cheatsheet-type-erasure-em-zig/"
markdown_url: "https://ziglang.com.br/padroes/cheatsheet-type-erasure-em-zig.MD"
description: "Design pattern Type Erasure implementado em Zig: apagar informação de tipo para interfaces genéricas em runtime, vtables manuais e polimorfismo sem herança. Guia completo em português."
date: "2026-02-21"
author: "Zig Brasil"
---

# Cheatsheet: Type Erasure em Zig

Design pattern Type Erasure implementado em Zig: apagar informação de tipo para interfaces genéricas em runtime, vtables manuais e polimorfismo sem herança. Guia completo em português.


# Type Erasure em Zig

Type Erasure é a técnica de "apagar" a informação de tipo concreto para criar interfaces genéricas que funcionam em runtime. Em linguagens com herança, isso é feito com classes abstratas/interfaces. Em Zig, sem herança, o padrão é implementado com **ponteiros opacos** (`*anyopaque`) e **ponteiros de função**, criando manualmente o equivalente a uma vtable.

## Quando Usar

- Coleções heterogêneas (diferentes tipos, mesma interface)
- Callbacks com contexto de tipo desconhecido
- Plugins e sistemas extensíveis em runtime
- Quando comptime generics não são suficientes (tipo decidido em runtime)

## A Técnica Fundamental

```zig
const std = @import("std");

// Interface "apagada" — não sabe o tipo concreto
const Writer = struct {
    ptr: *anyopaque,
    writeFn: *const fn (*anyopaque, []const u8) anyerror!usize,

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

    // Função helper para criar Writer a partir de qualquer tipo com write()
    pub fn init(ptr: anytype) Writer {
        const T = @TypeOf(ptr);
        const impl = struct {
            fn write(opaque: *anyopaque, dados: []const u8) anyerror!usize {
                const self: T = @ptrCast(@alignCast(opaque));
                return self.write(dados);
            }
        };
        return .{
            .ptr = @ptrCast(@alignCast(ptr)),
            .writeFn = impl.write,
        };
    }
};

// Tipo concreto 1
const FileWriter = struct {
    arquivo: std.fs.File,

    pub fn write(self: *FileWriter, dados: []const u8) !usize {
        return self.arquivo.write(dados);
    }

    pub fn writer(self: *FileWriter) Writer {
        return Writer.init(self);
    }
};

// Tipo concreto 2
const BufferWriter = struct {
    buffer: []u8,
    pos: usize = 0,

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

    pub fn writer(self: *BufferWriter) Writer {
        return Writer.init(self);
    }
};

// Código genérico que aceita qualquer "Writer"
fn escreverSaudacao(w: Writer) !void {
    _ = try w.write("Olá, mundo!\n");
}

pub fn main() !void {
    // Pode usar qualquer implementação
    var buffer: [100]u8 = undefined;
    var buf_writer = BufferWriter{ .buffer = &buffer };
    try escreverSaudacao(buf_writer.writer());

    std.debug.print("Buffer: {s}\n", .{buffer[0..buf_writer.pos]});
}
```

## Type Erasure na std

A biblioteca padrão de Zig usa type erasure extensivamente:

```zig
const std = @import("std");

// std.mem.Allocator é type erasure!
// Internamente: ptr: *anyopaque + vtable de funções
const allocator: std.mem.Allocator = undefined;

// std.io.Writer também é type erasure genérica
// std.io.Writer(Context, ErrorSet, writeFn)

// std.io.AnyWriter é type-erased writer
fn aceitaQualquerWriter(writer: std.io.AnyWriter) !void {
    try writer.writeAll("funciona com qualquer implementação\n");
}
```

## Interface Completa com Múltiplos Métodos

```zig
const std = @import("std");

const Forma = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    const VTable = struct {
        area: *const fn (*anyopaque) f64,
        perimetro: *const fn (*anyopaque) f64,
        nome: *const fn (*anyopaque) []const u8,
    };

    pub fn area(self: Forma) f64 {
        return self.vtable.area(self.ptr);
    }

    pub fn perimetro(self: Forma) f64 {
        return self.vtable.perimetro(self.ptr);
    }

    pub fn nome(self: Forma) []const u8 {
        return self.vtable.nome(self.ptr);
    }

    pub fn init(comptime T: type, ptr: *T) Forma {
        const impl = struct {
            fn area(opaque: *anyopaque) f64 {
                const self: *T = @ptrCast(@alignCast(opaque));
                return self.area();
            }
            fn perimetro(opaque: *anyopaque) f64 {
                const self: *T = @ptrCast(@alignCast(opaque));
                return self.perimetro();
            }
            fn nome(opaque: *anyopaque) []const u8 {
                const self: *T = @ptrCast(@alignCast(opaque));
                return self.nome();
            }
        };
        return .{
            .ptr = @ptrCast(ptr),
            .vtable = &.{
                .area = impl.area,
                .perimetro = impl.perimetro,
                .nome = impl.nome,
            },
        };
    }
};

const Circulo = struct {
    raio: f64,

    pub fn area(self: *Circulo) f64 {
        return std.math.pi * self.raio * self.raio;
    }
    pub fn perimetro(self: *Circulo) f64 {
        return 2.0 * std.math.pi * 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 perimetro(self: *Retangulo) f64 {
        return 2.0 * (self.largura + self.altura);
    }
    pub fn nome(_: *Retangulo) []const u8 {
        return "Retângulo";
    }
};

pub fn main() void {
    var circulo = Circulo{ .raio = 5.0 };
    var retangulo = Retangulo{ .largura = 4.0, .altura = 6.0 };

    // Coleção heterogênea de formas
    const formas = [_]Forma{
        Forma.init(Circulo, &circulo),
        Forma.init(Retangulo, &retangulo),
    };

    for (formas) |forma| {
        std.debug.print("{s}: área={d:.2}, perímetro={d:.2}\n", .{
            forma.nome(), forma.area(), forma.perimetro(),
        });
    }
}
```

## Quando Evitar

- Quando o tipo é conhecido em compilação — use comptime generics
- Quando há apenas um ou dois tipos — tagged union é mais simples
- Hot paths onde o overhead de indirection é significativo
- Quando a coleção heterogênea não é necessária

## Veja Também

- [Comptime](/cheatsheets/comptime/) — Polimorfismo estático sem overhead
- [Strategy](/padroes/strategy/) — Algoritmos intercambiáveis
- [Adapter](/padroes/adapter/) — Adaptar interfaces
- [Allocators](/cheatsheets/allocators/) — Exemplo real de type erasure
- [Enums e Unions](/cheatsheets/enums-unions/) — Tagged unions como alternativa
