@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
| Modifier | Descrição |
|---|---|
.auto | Comportamento padrão (igual a chamada normal) |
.always_inline | Forçar inlining (erro se não for possível) |
.never_inline | Nunca inline (sempre chamada de função) |
.never_tail | Nunca otimizar como tail call |
.compile_time | Forç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
- Performance: Forçar inlining de funções críticas em hot paths.
- Debugging: Prevenir tail calls para manter stack traces completos.
- Wrappers genéricos: Criar decoradores/interceptadores de funções.
- Benchmarks: Prevenir que o compilador otimize chamadas de teste.
- 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
- @import — Importar módulos
- @typeInfo — Introspecção para chamadas genéricas
- @TypeOf — Obter tipo de retorno de funções
- @compileError — Erro se inlining impossível