Type Coercion em Zig — O que é e Como Usar

Type Coercion em Zig — O que é e Como Usar

Definição

Type coercion (coerção de tipos) em Zig é o processo de converter um valor de um tipo para outro. Zig possui um sistema de coerção cuidadosamente projetado que permite conversões implícitas apenas quando são seguras e sem perda de informação. Para conversões que podem perder dados ou alterar a semântica, Zig exige conversão explícita usando builtins como @intCast, @floatFromInt, @ptrCast, etc.

Essa abordagem elimina uma classe inteira de bugs comuns em C, onde conversões implícitas silenciosas podem truncar valores ou mudar sinais.

Por que Type Coercion Importa

  1. Segurança: Conversões que podem perder dados exigem declaração explícita do programador.
  2. Clareza: O código expressa exatamente onde conversões acontecem.
  3. Previsibilidade: Sem surpresas de promoção implícita como em C (integer promotion rules).
  4. Portabilidade: O comportamento é o mesmo em todas as arquiteturas.

Exemplo Prático

Coerções Implícitas (Seguras)

const std = @import("std");

pub fn main() void {
    // comptime_int para qualquer tipo inteiro que comporte o valor
    const a: u32 = 42;
    const b: i64 = 42;

    // Widening: tipo menor para tipo maior (sem perda)
    const c: u16 = 100;
    const d: u32 = c; // u16 -> u32 implícito

    // Ponteiro para slice
    var array = [_]u8{ 1, 2, 3 };
    const slice: []u8 = &array; // *[3]u8 -> []u8 implícito

    // const qualification: T -> const T
    const ptr_mut: *u32 = @constCast(&a);
    _ = ptr_mut;

    std.debug.print("{} {} {}\n", .{ d, b, slice.len });
}

Coerções Explícitas (Potencialmente Inseguras)

const std = @import("std");

pub fn main() void {
    // Narrowing: tipo maior para tipo menor (pode perder dados)
    const grande: u32 = 200;
    const pequeno: u8 = @intCast(grande); // explícito!

    // Float para inteiro (perde parte fracionária)
    const pi: f64 = 3.14;
    const inteiro: i32 = @intFromFloat(pi); // 3

    // Inteiro para float
    const n: i32 = 42;
    const f: f64 = @floatFromInt(n); // 42.0

    std.debug.print("{} {} {d}\n", .{ pequeno, inteiro, f });
}

Coerção com Optionals e Error Unions

const std = @import("std");

fn buscar(id: u32) ?[]const u8 {
    if (id == 1) return "encontrado"; // []const u8 -> ?[]const u8 implícito
    return null;
}

fn calcular(x: u32) !u32 {
    if (x == 0) return error.DivisaoPorZero;
    return 100 / x; // u32 -> !u32 implícito
}

pub fn main() void {
    const resultado = buscar(1);
    if (resultado) |valor| {
        std.debug.print("{s}\n", .{valor});
    }

    const calc = calcular(5) catch 0;
    std.debug.print("{}\n", .{calc}); // 20
}

Regras de Coerção Implícita

DeParaPermitido
comptime_intQualquer inteiro que comporteSim
comptime_floatf32, f64Sim
u8u16, u32, u64Sim (widening)
u32u16, u8Apenas explícito (@intCast)
*[N]T[]TSim
T?TSim
TE!TSim
*T*const TSim

O Papel de @as na Coerção

O builtin @as(T, expr) força a coerção de expr para o tipo T. Isso é útil para resolver ambiguidades quando o compilador não consegue inferir o tipo alvo e para documentar intenção: @as(u32, 255) deixa claro que o valor é um u32, não um comptime_int genérico.

Comparação com Outras Linguagens

Em C, coerções implícitas são amplas e frequentemente silenciosas, levando a bugs clássicos como truncamento de inteiros e comparações de signed/unsigned sem aviso. Em Rust, as coerções implícitas são mais restritas que em C, mas as permite conversões explícitas sem panic mesmo se houver perda de dados. Já em Zig, qualquer narrowing exige um builtin explícito, e em modo Debug essas conversões são verificadas em runtime — @intCast causa panic se o valor não couber no tipo destino.

// Segurança em runtime (modo Debug):
const grande: u32 = 300;
const pequeno: u8 = @intCast(grande); // PANIC: 300 não cabe em u8

Em ReleaseFast, a verificação é removida por performance — por isso é importante testar em Debug primeiro.

Boas Práticas

  • Prefira coerções implícitas quando disponíveis: const s: []u8 = &array; é mais limpo que um cast explícito.
  • Use @intCast com consciência: Em modo Debug há checagem, mas em Release não. Valide os intervalos antes de converter.
  • Evite casts em cadeia: @intCast(@floatToInt(...)) obscurece a intenção. Faça um passo por vez com variáveis intermediárias.
  • Use @as para documentar: Em contextos ambíguos, @as(u32, valor) torna o tipo explícito e melhora a leitura do código.

Armadilhas Comuns

  • Comparar tipos diferentes: == entre u32 e i32 não é permitido. Converta explicitamente primeiro.
  • Aritmética mista: Somar u8 com u32 exige que ambos estejam no mesmo tipo. Use @as ou @intCast.
  • Literais negativos em unsigned: const x: u32 = -1; é erro de compilação. Zig não permite conversão implícita de valor negativo para unsigned.
  • Perda de precisão float: Converter f64 para f32 exige @floatCast, pois pode perder precisão.

Termos Relacionados

  • comptime_int — Tipo que pode ser coagido para qualquer inteiro
  • comptime_float — Tipo que pode ser coagido para qualquer float
  • anytype — Inferência de tipo para parâmetros genéricos
  • Pointer Types — Coerção entre tipos de ponteiro

Tutoriais Relacionados

Continue aprendendo Zig

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