Inline em Zig — O que é e Como Usar

Inline em Zig — O que é e Como Usar

Definição

A palavra-chave inline em Zig serve para duas finalidades principais: forçar o inlining de funções e desenrolar loops em tempo de compilação. Quando aplicada a uma função, o corpo da função é substituído diretamente no local da chamada. Quando aplicada a um for ou while, o loop é desenrolado (unrolled), e cada iteração é “colada” sequencialmente no código gerado.

Por que Inline Importa

  1. Performance: Elimina o custo de chamada de função (push/pop de stack frame, salto).
  2. Comptime em loops: inline for permite iterar sobre tipos e valores em tempo de compilação.
  3. Otimização manual: Quando o compilador não faz inline automaticamente, você pode forçar.
  4. Metaprogramação: Combinado com comptime, permite gerar código especializado.

Exemplo Prático

Inline For (Desenrolamento de Loop)

const std = @import("std");

fn somaVetorial(vetor: @Vector(4, f32)) f32 {
    // Desenrola em 4 somas individuais em tempo de compilação
    var resultado: f32 = 0;
    const campos = [_]comptime_int{ 0, 1, 2, 3 };
    inline for (campos) |i| {
        resultado += vetor[i];
    }
    return resultado;
}

pub fn main() void {
    const v: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 };
    std.debug.print("Soma: {d}\n", .{somaVetorial(v)}); // 10.0
}

Inline For com Tipos (Metaprogramação)

const std = @import("std");

fn imprimir(args: anytype) void {
    inline for (std.meta.fields(@TypeOf(args))) |campo| {
        std.debug.print("{s}: {}\n", .{ campo.name, @field(args, campo.name) });
    }
}

pub fn main() void {
    imprimir(.{ .nome = "Zig", .versao = 13, .estavel = true });
}

Inline Function

// Forçar inlining — o corpo será copiado no local da chamada
inline fn max(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
    return if (a > b) a else b;
}

pub fn main() void {
    // Não há chamada de função no código gerado
    const resultado = max(@as(u32, 10), @as(u32, 20));
    std.debug.print("Max: {}\n", .{resultado});
}

Inline While

fn contar(comptime n: usize) [n]usize {
    var resultado: [n]usize = undefined;
    comptime var i: usize = 0;
    inline while (i < n) : (i += 1) {
        resultado[i] = i * i;
    }
    return resultado;
}

const quadrados = contar(5); // [0, 1, 4, 9, 16]

Inline vs Comptime

Aspectoinlinecomptime
FunçõesSubstitui corpo no local da chamadaExecuta inteiramente na compilação
LoopsDesenrola iteraçõesAvalia valores em compilação
ResultadoCódigo mais rápido, binário maiorConstante embutida no binário

Na prática, inline for frequentemente trabalha em conjunto com valores comptime:

inline for (comptime std.meta.fieldNames(MeuTipo)) |nome| {
    // cada iteração é resolvida em compilação
}

Armadilhas Comuns

  • Binário inchado: Funções inline muito grandes, chamadas em muitos lugares, aumentam o tamanho do binário significativamente.
  • Inline desnecessário: O compilador de Zig (via LLVM) já faz inlining automático quando vantajoso. Use inline explícito apenas quando necessário.
  • Confundir inline for com for normal: inline for requer que o iterável seja conhecido em comptime. Um slice dinâmico não pode ser usado com inline for.
  • Debug mais difícil: Código inline não aparece como chamada de função separada no stack trace.

Quando Usar Inline

A escolha de quando aplicar inline manualmente deve ser cuidadosa. O compilador Zig (via LLVM) já realiza inlining automático em muitos casos, especialmente em modos de otimização como ReleaseFast. Use inline explícito apenas nas situações abaixo:

  • inline fn: Quando uma função pequena e crítica para performance precisa ser embutida independentemente do modo de compilação.
  • inline for: Quando você precisa iterar sobre uma tupla, lista de tipos ou valores conhecidos em comptime — pois loops normais não podem iterar sobre tipos.
  • inline while: Quando cada iteração precisa gerar código especializado diferente, ou quando a condição envolve um valor comptime.

Um caso de uso muito comum de inline for é implementar funções que aceitam anytype e precisam inspecionar os campos de uma struct em tempo de compilação:

const std = @import("std");

fn serializar(writer: anytype, valor: anytype) !void {
    const T = @TypeOf(valor);
    inline for (std.meta.fields(T)) |campo| {
        const v = @field(valor, campo.name);
        try writer.print("{s}={}\n", .{ campo.name, v });
    }
}

const Config = struct {
    porta: u16,
    debug: bool,
    versao: u32,
};

pub fn main() !void {
    const cfg = Config{ .porta = 8080, .debug = true, .versao = 2 };
    const writer = std.io.getStdOut().writer();
    try serializar(writer, cfg);
}

Comparação com Outras Linguagens

Em C e C++, a palavra-chave inline é apenas uma sugestão para o compilador — que pode ignorá-la. Em Zig, inline fn é uma garantia: o compilador é obrigado a embutir o corpo da função. Para loops, C/C++ não possuem equivalente nativo a inline for; o mais próximo seria macros ou templates, que são muito mais verbosos e menos seguros. Em Rust, o atributo #[inline(always)] tem comportamento semelhante ao inline fn do Zig.

Termos Relacionados

Tutoriais Relacionados

Continue aprendendo Zig

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