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
- Segurança: Conversões que podem perder dados exigem declaração explícita do programador.
- Clareza: O código expressa exatamente onde conversões acontecem.
- Previsibilidade: Sem surpresas de promoção implícita como em C (integer promotion rules).
- 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
| De | Para | Permitido |
|---|---|---|
comptime_int | Qualquer inteiro que comporte | Sim |
comptime_float | f32, f64 | Sim |
u8 | u16, u32, u64 | Sim (widening) |
u32 | u16, u8 | Apenas explícito (@intCast) |
*[N]T | []T | Sim |
T | ?T | Sim |
T | E!T | Sim |
*T | *const T | Sim |
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
@intCastcom 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
@aspara 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:
==entreu32ei32não é permitido. Converta explicitamente primeiro. - Aritmética mista: Somar
u8comu32exige que ambos estejam no mesmo tipo. Use@asou@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
f64paraf32exige@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