@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
Retorno de funções genéricas: Usar
@TypeOfpara definir o tipo de retorno de funções que operam sobreanytype, garantindo que o tipo de retorno seja consistente com os argumentos.Variáveis temporárias em código genérico: Declarar variáveis intermediárias com o tipo correto deduzido automaticamente.
Tipo comum para coerção: Com múltiplos argumentos, encontrar o tipo para o qual todos os valores podem ser convertidos.
Assertivas de tipo: Verificar em tempo de compilação que uma expressão tem o tipo esperado.
Metaprogramação: Combinar com
@typeInfopara 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
@TypeOfusa T maiúsculo por convenção do Zig: builtins que retornamtypeusam 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