@ptrCast em Zig
O @ptrCast converte um ponteiro de um tipo para outro. É a forma principal de reinterpretar dados na memória através de ponteiros com tipos diferentes. Esta é uma operação potencialmente insegura — use com cuidado e apenas quando necessário (interoperabilidade com C, interpretação de dados binários, etc.).
Sintaxe
@ptrCast(ptr: anytype) DestType
O tipo de destino é inferido do contexto (variável, retorno, parâmetro).
Parâmetros
- ptr (
anytype): O ponteiro a ser convertido. Deve ser um tipo de ponteiro válido.
Valor de retorno
Retorna o ponteiro convertido para o tipo de destino inferido do contexto.
Exemplos práticos
Exemplo 1: Reinterpretar struct como bytes
const std = @import("std");
const Cabecalho = packed struct {
versao: u8,
tipo: u8,
tamanho: u16,
};
pub fn main() void {
var cab = Cabecalho{
.versao = 1,
.tipo = 3,
.tamanho = 256,
};
// Reinterpretar a struct como bytes
const bytes: *[@sizeOf(Cabecalho)]u8 = @ptrCast(&cab);
std.debug.print("Bytes: {any}\n", .{bytes.*});
}
Exemplo 2: Interoperabilidade com C (anyopaque)
const std = @import("std");
const MeuDado = struct {
valor: u32,
nome: []const u8,
};
fn callback(ctx: *anyopaque) void {
// Converter de *anyopaque de volta para o tipo original
const dado: *MeuDado = @ptrCast(@alignCast(ctx));
std.debug.print("Valor: {}, Nome: {s}\n", .{ dado.valor, dado.nome });
}
pub fn main() void {
var dado = MeuDado{ .valor = 42, .nome = "teste" };
// Passar como *anyopaque (void* do C)
callback(@ptrCast(&dado));
}
Exemplo 3: Cast de ponteiro com vtable
const std = @import("std");
const Base = struct {
vtable: *const VTable,
const VTable = struct {
executarFn: *const fn (*anyopaque) void,
};
pub fn executar(self: *Base) void {
self.vtable.executarFn(@ptrCast(self));
}
};
Quando usar @ptrCast
- Interop com C: Converter
*anyopaque(void pointer) para tipos concretos. - Serialização binária: Interpretar bytes como structs ou vice-versa.
- Vtables: Converter ponteiros genéricos em callbacks.
- Alinhamento: Combinado com
@alignCastpara ajustar alinhamento.
Regras importantes
- Frequentemente combinado com
@alignCastquando o alinhamento do ponteiro de origem não corresponde ao destino. - Converter para um tipo incompatível e desreferenciar causa undefined behavior.
- Em modo safe (Debug/ReleaseSafe), casts inválidos são detectados em runtime.
Segurança e verificações em runtime
Em modo Debug e ReleaseSafe, o Zig verifica alinhamento automaticamente quando @alignCast é usado junto com @ptrCast. Se o endereço do ponteiro de origem não satisfaz o alinhamento requerido pelo tipo de destino, ocorre um panic em runtime — muito mais seguro que o undefined behavior silencioso do C.
Em ReleaseFast e ReleaseSmall, as verificações de alinhamento são removidas para máximo desempenho, então cabe ao programador garantir a corretude.
Comparação com equivalente em C
Em C, conversões de ponteiro são feitas com cast explícito e não oferecem nenhuma verificação:
// C: cast silencioso, sem verificação alguma
void *ctx = get_context();
MeuDado *dado = (MeuDado *)ctx; // pode causar UB por alinhamento ou tipo incorreto
Em Zig, a combinação de @ptrCast com @alignCast é explícita sobre o que está sendo feito, e o modo safe verifica o alinhamento em runtime:
// Zig: explícito e verificado em safe mode
const dado: *MeuDado = @ptrCast(@alignCast(ctx));
Padrão com @alignCast
A combinação mais comum de @ptrCast é com @alignCast ao recuperar ponteiros tipados de APIs que usam *anyopaque:
// Padrão correto ao recuperar contexto de callback
fn meuCallback(ctx: *anyopaque) void {
// @alignCast verifica o alinhamento em runtime (safe mode)
// @ptrCast converte o tipo do ponteiro
const dado: *MinhStruct = @ptrCast(@alignCast(ctx));
_ = dado;
}
Se o tipo de destino tiver alinhamento igual ou menor que *anyopaque (que é alinhamento 1), o @alignCast pode ser omitido, mas é boa prática mantê-lo para documentar a intenção.
Erros comuns
1. Esquecer @alignCast ao converter de *anyopaque:
// PODE CAUSAR PROBLEMA: sem verificação de alinhamento
const dado: *MeuTipo = @ptrCast(ctx); // aviso do compilador em versões recentes
// CORRETO: combinar com @alignCast
const dado: *MeuTipo = @ptrCast(@alignCast(ctx));
2. Confundir @ptrCast com @bitCast:
@ptrCast: converte o tipo do ponteiro, sem mover dados.@bitCast: reinterpreta os bits de um valor, sem ponteiros.
3. Usar @ptrCast para converter valores, não ponteiros:
// ERRADO: @ptrCast é apenas para ponteiros
// const x: u32 = @ptrCast(meu_float); // erro de compilação
// CORRETO para valores: usar @bitCast ou @as
const x: u32 = @bitCast(meu_float);
Perguntas Frequentes
P: Quando devo usar @ptrCast versus @as?
R: Use @as para coerções de tipo seguras entre tipos compatíveis (ex: u8 para u16). Use @ptrCast exclusivamente para converter entre tipos de ponteiro — quando você precisa reinterpretar a memória apontada como um tipo diferente.
P: @ptrCast pode converter entre ponteiros de tamanhos diferentes, como *u8 e []u8?
R: Não. @ptrCast converte ponteiros simples (*T) entre si. Para converter entre ponteiros e slices, ou ponteiros com sentinela, use @as com os tipos adequados ou construa o slice manualmente com ptr[0..len].
P: É seguro usar @ptrCast para serialização binária de structs?
R: Apenas com packed struct. Structs normais podem ter padding inserido pelo compilador, tornando a interpretação byte a byte imprevisível. Com packed struct, o layout é garantido e o cast é seguro.
Builtins relacionados
- @alignCast — Ajustar alinhamento do ponteiro
- @constCast — Remover qualificador const
- @volatileCast — Remover qualificador volatile
- @as — Conversão de tipo segura (coerção)
- @intFromPtr — Converter ponteiro para inteiro
- @ptrFromInt — Converter inteiro para ponteiro