@ptrCast em Zig — Referência e Exemplos

@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

  1. Interop com C: Converter *anyopaque (void pointer) para tipos concretos.
  2. Serialização binária: Interpretar bytes como structs ou vice-versa.
  3. Vtables: Converter ponteiros genéricos em callbacks.
  4. Alinhamento: Combinado com @alignCast para ajustar alinhamento.

Regras importantes

  • Frequentemente combinado com @alignCast quando 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

Tutoriais relacionados

Continue aprendendo Zig

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