@hasDecl em Zig — Referência e Exemplos

@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 booltrue 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

  1. Interfaces opcionais: Verificar se um tipo implementa métodos específicos antes de chamá-los.
  2. Código genérico adaptativo: Funções que se comportam diferente dependendo das capacidades do tipo.
  3. Frameworks e bibliotecas: Detectar hooks e callbacks opcionais definidos pelo usuário.
  4. Validação em comptime: Gerar @compileError se um tipo obrigatório não possuir uma declaração esperada.

Diferença entre @hasDecl e @hasField

É importante não confundir esses dois builtins:

  • @hasField verifica a existência de campos de dados (membros que armazenam valores em instâncias da struct).
  • @hasDecl verifica 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

Tutoriais relacionados

Continue aprendendo Zig

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