@hasDecl em Zig
O @hasDecl verifica em tempo de compilação se um tipo (geralmente um struct, enum ou union) possui uma declaração específica — seja uma função, constante ou tipo aninhado. Retorna true ou false, permitindo código genérico que se adapta às capacidades do tipo recebido.
Sintaxe
@hasDecl(comptime T: type, comptime name: []const u8) bool
Parâmetros
- T (
type, comptime): O tipo a ser inspecionado. - name (
[]const u8, comptime): O nome da declaração a verificar, como string.
Valor de retorno
Retorna bool — true se o tipo T possui uma declaração pública com o nome especificado, false caso contrário.
Exemplos práticos
Exemplo 1: Verificar se tipo tem método
const std = @import("std");
const Animal = struct {
nome: []const u8,
pub fn falar(self: Animal) []const u8 {
_ = self;
return "...";
}
};
const Pedra = struct {
peso: f64,
};
fn descrever(valor: anytype) void {
const T = @TypeOf(valor);
if (@hasDecl(T, "falar")) {
std.debug.print("Fala: {s}\n", .{valor.falar()});
} else {
std.debug.print("(sem fala)\n", .{});
}
}
pub fn main() void {
const gato = Animal{ .nome = "Gato" };
const pedra = Pedra{ .peso = 2.5 };
descrever(gato); // Fala: ...
descrever(pedra); // (sem fala)
}
Exemplo 2: Interface opcional com fallback
const std = @import("std");
fn formatar(valor: anytype) []const u8 {
const T = @TypeOf(valor);
if (@hasDecl(T, "formato")) {
return T.formato;
} else {
return @typeName(T);
}
}
const Celsius = struct {
pub const formato = "°C";
graus: f64,
};
const Ponto = struct {
x: f64,
y: f64,
};
pub fn main() void {
const temp = Celsius{ .graus = 25.0 };
const ponto = Ponto{ .x = 1, .y = 2 };
std.debug.print("{s}\n", .{formatar(temp)}); // °C
std.debug.print("{s}\n", .{formatar(ponto)}); // Ponto
}
Exemplo 3: Verificar presença de deinit
const std = @import("std");
fn limpar(valor: anytype) void {
const T = @TypeOf(value);
const info = @typeInfo(T);
if (info == .pointer) {
const Child = info.pointer.child;
if (@hasDecl(Child, "deinit")) {
valor.deinit();
}
}
}
Casos de uso comuns
- Interfaces opcionais: Verificar se um tipo implementa métodos específicos antes de chamá-los.
- Código genérico adaptativo: Funções que se comportam diferente dependendo das capacidades do tipo.
- Frameworks e bibliotecas: Detectar hooks e callbacks opcionais definidos pelo usuário.
- Validação em comptime: Gerar
@compileErrorse um tipo obrigatório não possuir uma declaração esperada.
Diferença entre @hasDecl e @hasField
É importante não confundir esses dois builtins:
@hasFieldverifica a existência de campos de dados (membros que armazenam valores em instâncias da struct).@hasDeclverifica a existência de declarações — funções, constantes, tipos aninhados e variáveis definidas no namespace do tipo.
const Exemplo = struct {
campo_de_dados: u32, // campo — verificado com @hasField
pub const CONSTANTE = 42; // declaração — verificada com @hasDecl
pub fn metodo(self: @This()) void { _ = self; } // declaração
};
// @hasField verifica campos de instância
_ = @hasField(Exemplo, "campo_de_dados"); // true
_ = @hasField(Exemplo, "CONSTANTE"); // false — CONSTANTE não é campo
// @hasDecl verifica declarações do namespace
_ = @hasDecl(Exemplo, "CONSTANTE"); // true
_ = @hasDecl(Exemplo, "metodo"); // true
_ = @hasDecl(Exemplo, "campo_de_dados"); // false — campo não é declaração
Padrão de interfaces em Zig com @hasDecl
Zig não tem interfaces formais, mas @hasDecl permite implementar um padrão similar de forma segura:
fn assertImplementaWriter(comptime T: type) void {
if (!@hasDecl(T, "write")) {
@compileError(@typeName(T) ++ " deve implementar o método 'write'");
}
if (!@hasDecl(T, "flush")) {
@compileError(@typeName(T) ++ " deve implementar o método 'flush'");
}
}
fn escreverComBuffer(comptime W: type, writer: W, dados: []const u8) !void {
comptime assertImplementaWriter(W);
// ... uso seguro de writer.write e writer.flush
try writer.write(dados);
try writer.flush();
}
Esse padrão garante erros de compilação claros quando um tipo não satisfaz os requisitos, em vez de erros obscuros sobre métodos não encontrados.
Considerações de desempenho
@hasDecl é avaliado exclusivamente em tempo de compilação e não tem nenhum custo em runtime. Blocos if (@hasDecl(...)) são equivalentes a comptime if, e o compilador gera apenas o código do ramo que será executado, eliminando dead code completamente.
Perguntas Frequentes
P: @hasDecl verifica declarações privadas (sem pub)?
Não para tipos externos. @hasDecl respeita a visibilidade: só retorna true para declarações que são acessíveis no contexto onde a verificação ocorre. Declarações pub são sempre verificáveis; declarações sem pub só são visíveis dentro do mesmo arquivo.
P: É possível verificar se um tipo tem uma declaração de um tipo específico (não apenas pelo nome)?
@hasDecl verifica apenas a existência pelo nome. Para verificar o tipo da declaração, use @hasDecl para confirmar a existência e depois @TypeOf(@field(T, "nome")) para obter o tipo da declaração e validá-lo com comptime.
P: @hasDecl funciona com módulos importados (resultado de @import)?
Sim. O resultado de @import é um tipo (a struct de nível superior do módulo), e @hasDecl pode verificar qualquer declaração pública nesse módulo.
Builtins relacionados
- @hasField — Verifica se um tipo possui um campo (não declaração)
- @typeInfo — Informações detalhadas sobre um tipo
- @typeName — Nome de um tipo como string
- @field — Acessa campo por nome em comptime