@TypeOf em Zig — Referência e Exemplos

@TypeOf em Zig

O @TypeOf é um builtin que deduz e retorna o tipo de qualquer expressão em tempo de compilação. É o equivalente ao decltype do C++ ou ao typeof do JavaScript, mas resolvido estaticamente pelo compilador. Note que @TypeOf é escrito com T maiúsculo, diferente da maioria dos outros builtins — isso porque ele retorna um type, que em Zig é um valor de primeira classe em comptime.

Sintaxe

@TypeOf(expressao) type

Também aceita múltiplas expressões:

@TypeOf(expr1, expr2, ..., exprN) type

O que faz

O @TypeOf avalia o tipo de uma expressão sem executá-la. Quando recebe múltiplas expressões, retorna o tipo resultante da resolução de coerção entre todos os tipos — ou seja, o tipo para o qual todas as expressões podem ser implicitamente convertidas.

A expressão fornecida não é executada; apenas seu tipo é analisado. Isso significa que efeitos colaterais dentro da expressão não ocorrem.

Parâmetros

  • expressao (qualquer expressão válida): Uma ou mais expressões cujo tipo será deduzido. As expressões devem ser válidas sintaticamente e ter tipo determinável em tempo de compilação.

Valor de retorno

Retorna um type — o tipo deduzido da expressão (ou o tipo comum quando múltiplas expressões são fornecidas).

Exemplos práticos

Exemplo 1: Dedução básica de tipos

const std = @import("std");

test "dedução de tipo com @TypeOf" {
    const x: i32 = 42;
    const y = @as(f64, 3.14);
    const z = "Olá, Zig!";

    // @TypeOf deduz o tipo de cada expressão
    try std.testing.expect(@TypeOf(x) == i32);
    try std.testing.expect(@TypeOf(y) == f64);
    try std.testing.expect(@TypeOf(z) == *const [9:0]u8);

    // Funciona com expressões compostas
    try std.testing.expect(@TypeOf(x + 10) == i32);
    try std.testing.expect(@TypeOf(x > 0) == bool);
}

Exemplo 2: Criando variáveis com tipo deduzido

const std = @import("std");

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

fn criarArray(comptime tamanho: usize) [@TypeOf(tamanho)]u8 {
    // Aqui @TypeOf(tamanho) é usize, mas o uso prático é diferente
    // Geralmente @TypeOf é mais útil para retornos de funções genéricas
    return undefined;
}

test "retorno com tipo deduzido" {
    const resultado = calcular(@as(i32, 10), @as(i32, 20));
    try std.testing.expect(@TypeOf(resultado) == i32);
}

Exemplo 3: Tipo comum entre múltiplas expressões

const std = @import("std");

test "tipo comum com @TypeOf" {
    const a: i16 = 10;
    const b: i32 = 20;

    // Com múltiplos argumentos, retorna o tipo de coerção comum
    const TipoComum = @TypeOf(a, b);

    // i16 pode ser implicitamente convertido para i32, então o tipo comum é i32
    try std.testing.expect(TipoComum == i32);

    // Isso é útil para determinar o tipo de retorno em operações genéricas
    var resultado: TipoComum = a;
    resultado += b;
    try std.testing.expect(resultado == 30);
}

Casos de uso comuns

  1. Retorno de funções genéricas: Usar @TypeOf para definir o tipo de retorno de funções que operam sobre anytype, garantindo que o tipo de retorno seja consistente com os argumentos.

  2. Variáveis temporárias em código genérico: Declarar variáveis intermediárias com o tipo correto deduzido automaticamente.

  3. Tipo comum para coerção: Com múltiplos argumentos, encontrar o tipo para o qual todos os valores podem ser convertidos.

  4. Assertivas de tipo: Verificar em tempo de compilação que uma expressão tem o tipo esperado.

  5. Metaprogramação: Combinar com @typeInfo para inspecionar o tipo de expressões arbitrárias.

fn ehInteiro(valor: anytype) bool {
    return switch (@typeInfo(@TypeOf(valor))) {
        .int, .comptime_int => true,
        else => false,
    };
}

Observações importantes

  • O @TypeOf usa T maiúsculo por convenção do Zig: builtins que retornam type usam PascalCase.
  • A expressão passada não é executada; não há efeitos colaterais.
  • Quando múltiplos argumentos são passados e não existe um tipo comum de coerção, o compilador emite um erro.

Uso com anytype em funções genéricas

O uso mais poderoso de @TypeOf é em funções que aceitam anytype. Como o compilador precisa gerar código específico para cada tipo, @TypeOf permite expressar o tipo de retorno em função dos argumentos:

const std = @import("std");

// Retorna o maior dos dois valores, com tipo preservado
fn maximo(a: anytype, b: anytype) @TypeOf(a, b) {
    return if (a > b) a else b;
}

// Clamp genérico: limita valor entre min e max
fn clamp(valor: anytype, minimo: anytype, maximo_val: anytype) @TypeOf(valor, minimo, maximo_val) {
    if (valor < minimo) return minimo;
    if (valor > maximo_val) return maximo_val;
    return valor;
}

test "funções genéricas com @TypeOf" {
    try std.testing.expect(maximo(@as(i32, 10), @as(i32, 20)) == 20);
    try std.testing.expect(clamp(@as(f32, 1.5), @as(f32, 0.0), @as(f32, 1.0)) == 1.0);
}

Combinação com @typeInfo para verificações em comptime

A combinação @typeInfo(@TypeOf(valor)) é o padrão idiomático para inspecionar o tipo de um argumento anytype e tomar decisões em comptime:

const std = @import("std");

fn imprimir(valor: anytype) void {
    const T = @TypeOf(valor);
    switch (@typeInfo(T)) {
        .int, .comptime_int => std.debug.print("inteiro: {}\n", .{valor}),
        .float, .comptime_float => std.debug.print("float: {d:.2}\n", .{valor}),
        .bool => std.debug.print("bool: {}\n", .{valor}),
        .pointer => |ptr| {
            if (ptr.size == .slice and ptr.child == u8) {
                std.debug.print("string: {s}\n", .{valor});
            } else {
                std.debug.print("ponteiro: *{s}\n", .{@typeName(ptr.child)});
            }
        },
        .@"struct" => std.debug.print("struct: {any}\n", .{valor}),
        else => std.debug.print("outro: {any}\n", .{valor}),
    }
}

test "imprimir genérico" {
    imprimir(@as(i32, 42));
    imprimir(@as(f64, 3.14));
    imprimir(true);
    imprimir("Olá, Zig!");
}

Diferença entre @TypeOf e inferência de tipo com var/const

Em Zig, const x = expr já infere o tipo de x automaticamente. @TypeOf é necessário quando você precisa usar o tipo em outros contextos — como parâmetros de função, tipos de retorno, ou para raciocinar explicitamente sobre ele:

// Inferência de tipo automática (mais comum)
const x = @as(i32, 42); // x tem tipo i32, inferido automaticamente

// @TypeOf é necessário quando você precisa do tipo como valor
const T = @TypeOf(x);        // T == i32, um type como valor comptime
const info = @typeInfo(T);   // inspecionar o tipo
const nome = @typeName(T);   // obter o nome como string

Comportamento com expressões de efeito colateral

Uma propriedade importante: @TypeOf não executa a expressão. Isso significa que efeitos colaterais (chamadas de função, modificações de variáveis) dentro da expressão passada para @TypeOf não ocorrem:

var contador: u32 = 0;

fn incrementar() u32 {
    contador += 1; // este efeito colateral NÃO ocorre dentro de @TypeOf
    return contador;
}

// A expressão é analisada sintaticamente para determinar o tipo,
// mas a função não é chamada
const T = @TypeOf(incrementar()); // T == u32, mas incrementar() não executa

Comparação com equivalente em C/C++

Em C, não há equivalente direto. Em C++, existe decltype:

// C++: decltype
int x = 42;
decltype(x) y = 10;  // y tem tipo int
decltype(x + 3.14) z; // z tem tipo double (coerção implícita)

Em Zig, @TypeOf é semanticamente similar ao decltype do C++, mas com a vantagem de trabalhar com o sistema de tipos mais rigoroso do Zig e sem coerções implícitas inesperadas:

// Zig: @TypeOf
const x: i32 = 42;
const T = @TypeOf(x);    // T == i32
var y: T = 10;           // y tem tipo i32, explícito

Perguntas Frequentes

P: Por que @TypeOf usa T maiúsculo enquanto outros builtins usam minúscula?

R: Por convenção do Zig, builtins que retornam type usam PascalCase. Como type é um valor de primeira classe em Zig (pode ser armazenado em variáveis, passado como argumento), a convenção PascalCase sinaliza que o resultado é um tipo. Outros exemplos: @This(), @Frame().

P: O que acontece se as expressões passadas para @TypeOf não têm tipo comum?

R: O compilador emite um erro. Por exemplo, @TypeOf(@as(i32, 1), "texto") falha porque i32 e *const [5:0]u8 não têm tipo comum por coerção implícita.

P: @TypeOf pode ser usado para deduzir o tipo de retorno de uma função recursiva?

R: Em geral, não para funções com recursão não resolvida, pois criaria circularidade na inferência. Para funções recursivas, declare o tipo de retorno explicitamente.

Builtins relacionados

  • @typeInfo — Obtém informações detalhadas sobre o tipo retornado
  • @typeName — Obtém o nome do tipo como string
  • @as — Conversão explícita para um tipo específico
  • @field — Acessa campo por nome dinâmico

Tutoriais relacionados

Continue aprendendo Zig

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