@call em Zig — Referência e Exemplos

@call em Zig

O @call invoca uma função com controle avançado sobre como a chamada é realizada. Permite forçar inlining, prevenir tail calls, controlar otimizações de chamada e especificar o stack frame. Útil para código de alta performance, recursão e interoperabilidade.

Sintaxe

@call(modifier: std.builtin.CallModifier, function: anytype, args: anytype) ReturnType

Parâmetros

  • modifier (CallModifier): Controle sobre como a chamada é feita.
  • function: A função a ser chamada.
  • args: Tupla com os argumentos da função.

Valor de retorno

Retorna o mesmo tipo que a função chamada retornaria.

Modifiers disponíveis

ModifierDescrição
.autoComportamento padrão (igual a chamada normal)
.always_inlineForçar inlining (erro se não for possível)
.never_inlineNunca inline (sempre chamada de função)
.never_tailNunca otimizar como tail call
.compile_timeForçar avaliação em comptime

Exemplos práticos

Exemplo 1: Forçar inlining

const std = @import("std");

fn somaRapida(a: u64, b: u64) u64 {
    return a + b;
}

pub fn main() void {
    // Forçar que somaRapida seja inlined
    const resultado = @call(.always_inline, somaRapida, .{
        @as(u64, 42),
        @as(u64, 58),
    });

    std.debug.print("Soma: {}\n", .{resultado}); // 100
}

Exemplo 2: Prevenir tail call em recursão

const std = @import("std");

fn fatorial(n: u64) u64 {
    if (n <= 1) return 1;

    // Prevenir tail call para manter stack frame (útil para debug)
    return n * @call(.never_tail, fatorial, .{n - 1});
}

pub fn main() void {
    std.debug.print("10! = {}\n", .{fatorial(10)}); // 3628800
}

Exemplo 3: Chamada genérica de função

const std = @import("std");

fn chamarComLog(funcao: anytype, args: anytype) @TypeOf(@call(.auto, funcao, args)) {
    std.debug.print("Chamando função...\n", .{});
    const resultado = @call(.auto, funcao, args);
    std.debug.print("Função retornou.\n", .{});
    return resultado;
}

fn multiplicar(a: i32, b: i32) i32 {
    return a * b;
}

fn saudar(nome: []const u8) void {
    std.debug.print("Olá, {s}!\n", .{nome});
}

pub fn main() void {
    const produto = chamarComLog(multiplicar, .{ @as(i32, 6), @as(i32, 7) });
    std.debug.print("Produto: {}\n", .{produto}); // 42

    chamarComLog(saudar, .{"Zig Brasil"});
}

Casos de uso comuns

  1. Performance: Forçar inlining de funções críticas em hot paths.
  2. Debugging: Prevenir tail calls para manter stack traces completos.
  3. Wrappers genéricos: Criar decoradores/interceptadores de funções.
  4. Benchmarks: Prevenir que o compilador otimize chamadas de teste.
  5. Recursão controlada: Garantir que recursão use o stack conforme esperado.

@call vs chamada normal

// Equivalentes:
const r1 = funcao(a, b);
const r2 = @call(.auto, funcao, .{ a, b });

// Mas @call permite controle extra:
const r3 = @call(.always_inline, funcao, .{ a, b });  // Forçar inline
const r4 = @call(.never_tail, funcao, .{ a, b });     // Sem tail call

Quando usar cada modifier

.auto: Use quando não precisa de controle especial. Equivalente à chamada direta funcao(args).

.always_inline: Use em funções pequenas e críticas de performance que devem eliminar o overhead de chamada. O compilador emite erro se não for possível fazer inline (por exemplo, funções recursivas não podem ser inlined).

.never_inline: Use para:

  • Forçar que uma função seja um símbolo separado no binário (útil para profiling)
  • Evitar crescimento excessivo do código em debug builds
  • Garantir que o stack trace apareça corretamente

.never_tail: Use quando a semântica de tail call quebraria a lógica. Por exemplo, em código que usa defer ou que depende do stack frame após o retorno.

.compile_time: Use quando quer garantir que uma função seja avaliada em comptime. Útil para verificar que uma otimização comptime está sendo aplicada.

Diferença entre @call e inline keyword

O Zig tem duas formas de forçar inlining:

// Keyword inline na definição — sempre inline, sem escolha
inline fn somaRapida(a: u32, b: u32) u32 { return a + b; }

// @call .always_inline — inline no ponto de chamada
fn soma(a: u32, b: u32) u32 { return a + b; }
const r = @call(.always_inline, soma, .{a, b});

A diferença: inline fn força inline em todos os call sites. @call(.always_inline, ...) força inline apenas naquele call site específico, deixando outros call sites com o comportamento padrão.

@call com ponteiros de função

@call é especialmente útil com ponteiros de função, onde chamadas normais nunca são inlined:

const Transformador = *const fn(u32) u32;

fn aplicar(func: Transformador, valor: u32) u32 {
    // Aqui, @call(.auto, func, .{valor}) é igual a func(valor)
    // Mas com .always_inline, o compilador pode tentar inline via PGO
    return @call(.auto, func, .{valor});
}

Equivalente em C

Em C, __attribute__((always_inline)) e __attribute__((noinline)) são colocados na definição da função, não no call site. O Zig oferece mais granularidade com @call.

Erros comuns

Usar .always_inline em funções recursivas: O compilador emitirá um erro porque funções recursivas não podem ser inlined completamente. Use .never_tail em vez disso para controlar a recursão.

Esquecer que .compile_time exige que os argumentos sejam comptime: Se os argumentos não forem conhecidos em tempo de compilação, o compilador emitirá um erro.

Perguntas Frequentes

@call tem overhead em comparação com uma chamada normal?

Com .auto, não há overhead — o compilador gera código idêntico. Com outros modifiers, pode haver custo ou benefício dependendo do contexto.

Posso usar @call com métodos de struct?

Sim. Passe a função explicitamente como Struct.metodo e inclua self nos argumentos:

const r = @call(.always_inline, MeuTipo.calcular, .{&instancia, argumento});

Builtins relacionados

Tutoriais relacionados

Continue aprendendo Zig

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