anytype em Zig: o que é, exemplos e vs comptime T

anytype em Zig permite inferir o tipo de um parâmetro a partir do argumento da chamada, como em fn dobrar(x: anytype) @TypeOf(x). O tipo é conhecido em tempo de compilação, mas o valor pode chegar em runtime. Não é tipagem dinâmica nem o aplicativo de notas Anytype: é uma forma de escrever funções genéricas sem <T>.

Use anytype quando a função só precisa aceitar o valor passado e operar sobre ele — writers, helpers matemáticos, formatadores. Se vários parâmetros precisam do mesmo tipo nomeado, prefira comptime T: type. Referência rápida: cheatsheet de comptime.

Resumo rápido

PerguntaResposta curta
O que é anytype?Parâmetro genérico com tipo inferido na chamada
É o app Anytype?Não — aqui é a keyword da linguagem Zig
Tipo ou valor em comptime?O tipo é conhecido em comptime; o valor pode ser de runtime
Como ler o tipo?@TypeOf(param) e @typeInfo
Alternativa explícitacomptime T: type
Uso clássicoWriters, print/log, helpers genéricos
Parecido comTemplates C++ / generics Rust, sem <T>

Definição

anytype é uma anotação de tipo especial em Zig que permite que o compilador infira o tipo de um parâmetro a partir do argumento passado na chamada da função. É o mecanismo principal para criar funções genéricas em Zig sem precisar de uma sintaxe separada de templates.

Quando uma função aceita anytype, o compilador gera uma versão especializada para cada combinação de tipos usada. Isso se aproxima de templates em C++, com sintaxe mais enxuta e erros que normalmente apontam para o uso concreto do valor.

anytype em Zig ≠ app Anytype

Se a busca por anytype trouxe você até aqui por causa do aplicativo de notas Anytype, este verbete trata de outra coisa: a keyword da linguagem Zig. O app Anytype não entra na sintaxe, no compilador nem na biblioteca padrão do Zig. Daqui em diante, anytype significa apenas o parâmetro genérico com tipo inferido.

Por que anytype importa

  1. Genéricos simples: cria funções que aceitam vários tipos sem boilerplate de <T>.
  2. Duck typing em comptime: o compilador verifica se o tipo realmente oferece as operações usadas.
  3. Zero overhead: cada especialização é compilada individualmente, sem vtable obrigatória.
  4. Composição: combina com @TypeOf e @typeInfo para introspecção e validação.

Exemplos práticos de anytype

Os quatro programas completos abaixo foram compilados e executados com Zig 0.15.2. Salve cada um separadamente como anytype.zig e execute zig run anytype.zig. A interface de I/O varia entre versões; o exemplo de writer usa std.Io.Writer, não o antigo std.io.fixedBufferStream.

Função genérica com anytype

const std = @import("std");

fn dobrar(valor: anytype) @TypeOf(valor) {
    return valor * 2;
}

pub fn main() void {
    const a: u32 = dobrar(@as(u32, 21));
    const b: f64 = dobrar(@as(f64, 1.5));
    const c: i8 = dobrar(@as(i8, -10));

    std.debug.print("u32: {}\n", .{a}); // 42
    std.debug.print("f64: {d}\n", .{b}); // 3.0
    std.debug.print("i8: {}\n", .{c}); // -20
}

anytype com introspecção de tipo

const std = @import("std");

fn imprimir(valor: anytype) void {
    const T = @TypeOf(valor);
    const info = @typeInfo(T);

    switch (info) {
        .int, .comptime_int => std.debug.print("Inteiro: {}\n", .{valor}),
        .float, .comptime_float => std.debug.print("Float: {d}\n", .{valor}),
        .pointer => |ptr_info| {
            if (ptr_info.size == .slice and ptr_info.child == u8) {
                std.debug.print("String: {s}\n", .{valor});
            } else {
                std.debug.print("Ponteiro: {*}\n", .{valor});
            }
        },
        .bool => std.debug.print("Bool: {}\n", .{valor}),
        else => std.debug.print("Tipo: {s}\n", .{@typeName(T)}),
    }
}

pub fn main() void {
    imprimir(@as(u32, 42));
    imprimir(@as(f64, 3.14));
    imprimir(true);
    imprimir(@as([]const u8, "Zig"));
}

O cast para []const u8 faz a string cair no ramo .slice. Sem o cast, o literal "Zig" tem tipo *const [3:0]u8 (ponteiro para array com sentinela), e não um slice.

Writer genérico com anytype

const std = @import("std");

fn escreverSaudacao(writer: anytype, nome: []const u8) !void {
    try writer.print("Olá, {s}! Bem-vindo ao Zig.\n", .{nome});
}

pub fn main() !void {
    var buffer: [128]u8 = undefined;
    var writer = std.Io.Writer.fixed(&buffer);
    try escreverSaudacao(&writer, "Brasil");

    std.debug.print("{s}", .{writer.buffered()});
}

Aqui, anytype exige apenas que o argumento permita a chamada writer.print(...); isso é verificado quando a função é instanciada. Se a API aceita somente a interface std.Io.Writer, declarar writer: *std.Io.Writer torna o contrato explícito e dispensa esse genérico. Use anytype quando realmente quiser aceitar diferentes tipos compatíveis. Veja também o guia de migração de writers.

anytype vs comptime T: type

Aspectoanytypecomptime T: type
SintaxeImplícitaExplícita
Acesso ao tipoVia @TypeOf(param)Diretamente via T
Vários parâmetros do mesmo tipoDois anytype são independentes; b: @TypeOf(a) relaciona os tiposa: T, b: T exige argumentos compatíveis com T
Uso típicoFunções curtas, writers, helpersContêineres e APIs com tipo nomeado

Regra prática: comece com anytype quando a assinatura fica mais clara sem nomear o tipo; mude para comptime T: type quando o tipo precisa ser reutilizado, documentado ou compartilhado entre campos e parâmetros.

Estas três assinaturas deixam a diferença concreta:

fn independente(a: anytype, b: anytype) void { // tipos independentes
    _ = a;
    _ = b;
}

fn inferido(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
    return a + b;
}

fn explicito(comptime T: type, a: T, b: T) T {
    return a + b;
}

Por exemplo, inferido(@as(u32, 10), 20) e explicito(u32, 10, 20) retornam 30. O literal 20 pode ser convertido para u32; não é necessário que todo argumento tenha originalmente esse tipo.

anytype não exige um valor comptime

valor: anytype infere o tipo; comptime valor: anytype exige também que o valor seja conhecido na compilação. Esta função aceita normalmente uma variável modificada em runtime:

const std = @import("std");

fn dobrar(valor: anytype) @TypeOf(valor) {
    return valor * 2;
}

test "anytype aceita valor de runtime" {
    var numero: u32 = 20;
    numero += 1;
    try std.testing.expectEqual(@as(u32, 42), dobrar(numero));
}

Salve esse teste em anytype-teste.zig e rode zig test anytype-teste.zig. @TypeOf(numero) é u32, mesmo que o valor de numero não seja uma constante de compilação.

Cuidado com literais numéricos

Em dobrar(21), o parâmetro é inferido como comptime_int, não como u32. Se o corpo depender de dados de runtime, esse tipo pode causar um erro como unable to resolve comptime value. Para escolher um inteiro de runtime, passe dobrar(@as(u32, 21)) ou uma variável tipada. Para floats, o equivalente é @as(f64, 1.5).

Validando tipos com @compileError

Quando anytype recebe um tipo inadequado, o erro padrão pode apontar para uma linha interna pouco amigável. Valide cedo:

const std = @import("std");

fn somar(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
    const T = @TypeOf(a);
    const info = @typeInfo(T);

    if (info != .int and info != .float and info != .comptime_int and info != .comptime_float) {
        @compileError("somar() requer tipo numérico, recebeu: " ++ @typeName(T));
    }

    return a + b;
}

pub fn main() void {
    std.debug.print("{}\n", .{somar(@as(u32, 10), @as(u32, 20))});
}

Neste exemplo, b: @TypeOf(a) garante que o segundo argumento seja compatível com o tipo do primeiro. A validação cobre o tipo numérico, não overflow: u32 continua tendo os limites de u32. Para verificar a mensagem de erro, substitua a chamada por somar(true, false); a compilação deve falhar com somar() requer tipo numérico, recebeu: bool.

Casos de uso típicos

  • Funções matemáticas: fn abs(x: anytype) @TypeOf(x) para tipos numéricos.
  • Writers genéricos: funções que aceitam qualquer valor com .print / .writeAll.
  • Logging e formatação: helpers que formatam valores arbitrários em comptime.
  • Wrappers: decorators que envolvem outro valor sem conhecer o tipo concreto.

Para o mecanismo mais amplo por trás disso, veja o guia de comptime em Zig e a página de reflexão com comptime.

Armadilhas comuns

  • Erros difíceis de ler: valide com @typeInfo e @compileError no início da função.
  • Dois anytype diferentes: dois parâmetros anytype podem receber tipos distintos; use b: @TypeOf(a) ou comptime T: type com a: T, b: T para relacioná-los.
  • Não é um tipo para variáveis ou campos: var x: anytype e struct { x: anytype } não são declarações válidas. Use um tipo concreto ou uma função que retorna um tipo genérico.
  • Não é dinâmico: anytype não cria um objeto “qualquer” em runtime.
  • Tipo de retorno: use @TypeOf(param) quando o retorno deve acompanhar o argumento.
  • APIs de I/O em evolução: em Zig 0.16, writers e std.Io mudaram; ao copiar exemplos antigos com getStdOut().writer(), confira o guia de migração Writergate e as novidades do Zig 0.16.

Perguntas frequentes

O que é anytype em Zig?

anytype é o parâmetro cujo tipo o compilador infere na chamada. É a forma mais direta de escrever genéricos implícitos em Zig.

anytype em Zig é o mesmo que o app Anytype?

Não. Aqui anytype é uma palavra-chave da linguagem Zig. O aplicativo Anytype, de notas e bases de conhecimento, é um produto separado e não faz parte da sintaxe do Zig.

anytype é tipagem dinâmica?

Não. O tipo é resolvido estaticamente e não há boxing nem dispatch dinâmico implícito. Isso não exige que o valor seja conhecido em comptime: uma variável u32 de runtime também pode ser passada para anytype.

Quando preferir comptime T: type?

Quando o tipo precisa de nome, aparece em vários parâmetros/campos ou faz parte da API pública de um contêiner genérico.

Como inspecionar o tipo inferido?

Use @TypeOf(valor) para obter o tipo e @typeInfo para ler a tagged union descritiva desse tipo. Veja também o builtin @typeInfo.

Por que o erro de anytype às vezes é confuso?

Porque a falha aparece no ponto em que a operação incompatível é usada, não necessariamente na assinatura. Valide cedo com @typeInfo + @compileError (veja a seção acima) para mensagens acionáveis.

anytype em Zig é igual a template C++ ou generic Rust?

Conceitualmente sim: o compilador gera uma especialização por tipo. A diferença prática é a sintaxe e o duck typing em comptime — em Zig você escreve anytype (ou comptime T: type) e o corpo da função define quais operações o tipo precisa oferecer.

Termos relacionados

  • Struct — tipos compostos e genéricos com comptime
  • Tagged Union — unions discriminadas usadas em introspecção
  • Sentinel — slices e arrays terminados por sentinela
  • usize — inteiro de tamanho nativo para índices e tamanhos

Tutoriais relacionados

Referência oficial

Continue aprendendo Zig

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