Pointer Types em Zig — O que é e Como Usar
Definição
Pointer types (tipos de ponteiro) em Zig são referências a endereços de memória. Diferente de C, onde existe apenas um tipo de ponteiro (T*), Zig distingue entre vários tipos de ponteiro com semânticas diferentes. Essa distinção permite ao compilador verificar a segurança do acesso à memória e otimizar o código gerado.
Cada tipo de ponteiro carrega informações sobre: quantos elementos aponta, se é mutável ou const, qual o alinhamento, e se pode ser nulo.
Por que Pointer Types Importam
- Segurança: O sistema de tipos impede usos incorretos de ponteiros em tempo de compilação.
- Clareza: O tipo declara exatamente a intenção — um item, vários itens ou um slice com tamanho.
- Interop com C: Tipos específicos mapeiam diretamente para ponteiros C.
- Otimização: O compilador usa as informações de tipo para gerar código mais eficiente.
Tipos de Ponteiro
| Tipo | Nome | Descrição |
|---|---|---|
*T | Single-item pointer | Aponta para exatamente um valor |
*const T | Const pointer | Ponteiro somente leitura para um valor |
[*]T | Many-item pointer | Aponta para vários itens (sem tamanho conhecido) |
[]T | Slice | Ponteiro + comprimento (tamanho conhecido) |
[*:0]T | Sentinel-terminated pointer | Muitos itens terminados por sentinela |
?*T | Optional pointer | Pode ser nulo |
Exemplo Prático
Single-Item Pointer (*T)
const std = @import("std");
fn incrementar(ptr: *u32) void {
ptr.* += 1;
}
pub fn main() void {
var valor: u32 = 10;
incrementar(&valor);
std.debug.print("Valor: {}\n", .{valor}); // 11
// Ponteiro const — somente leitura
const constante: u32 = 42;
const ptr: *const u32 = &constante;
std.debug.print("Lido: {}\n", .{ptr.*}); // 42
}
Many-Item Pointer ([*]T) e Slice ([]T)
const std = @import("std");
pub fn main() void {
var array = [_]u32{ 10, 20, 30, 40, 50 };
// Slice: ponteiro + tamanho
const slice: []u32 = array[1..4]; // [20, 30, 40]
std.debug.print("Slice len: {}\n", .{slice.len}); // 3
// Many-item pointer: sem tamanho
const many: [*]u32 = &array;
std.debug.print("many[2] = {}\n", .{many[2]}); // 30
// Converter many-pointer para slice
const slice2: []u32 = many[0..5];
std.debug.print("Slice2 len: {}\n", .{slice2.len}); // 5
}
Ponteiros Opcionais (?*T)
const std = @import("std");
const Node = struct {
valor: i32,
proximo: ?*Node, // Pode ser nulo
};
fn somar_lista(inicio: ?*Node) i32 {
var soma: i32 = 0;
var atual = inicio;
while (atual) |node| {
soma += node.valor;
atual = node.proximo;
}
return soma;
}
pub fn main() void {
var c = Node{ .valor = 3, .proximo = null };
var b = Node{ .valor = 2, .proximo = &c };
var a = Node{ .valor = 1, .proximo = &b };
const total = somar_lista(&a);
std.debug.print("Soma: {}\n", .{total}); // 6
}
Coerções de Ponteiro
*[N]T --> []T (array pointer para slice)
*[N]T --> [*]T (array pointer para many-pointer)
*T --> *const T (mutável para const)
[]T --> []const T (slice mutável para const)
Sentinel-Terminated Pointer ([*:0]T)
O tipo [*:0]u8 é o equivalente de char* em C — um ponteiro para bytes terminados por zero (null-terminated string). Zig usa este tipo para interoperabilidade com APIs C. Na prática, ao chamar funções C que retornam char*, Zig representa o tipo como [*:0]u8. Use std.mem.span(ptr) para converter para []u8 de forma segura ao encontrar o terminador.
Comparação com Ponteiros em C
| Recurso | C T* | Zig *T | Zig []T | Zig [*]T |
|---|---|---|---|---|
| Tamanho conhecido | Não | 1 elemento | Sim (.len) | Não |
| Pode ser nulo | Sim | Não (?*T) | Não | Não |
| Aritmética | Sim | Não | Via slicing | Sim |
| Verificação bounds | Não | Não | Debug | Não |
| Null-terminated | Convencional | Não | Não | [*:0]T |
Zig separa o que C trata como um único conceito em tipos distintos, tornando a intenção do código explícita e verificável pelo compilador.
Boas Práticas
- Prefira
[]T(slice) sobre[*]T: Slices carregam o tamanho e permitem iteração segura comfor. Use[*]Tapenas para interop com C ou performance extrema onde o tamanho é gerenciado externamente. - Use
?*Tpara representar nulidade: Nunca use0ou@intToPtrcomo substituto de null. O tipo?*Tcomunica a intenção e força verificação explícita. - Converta
[*:0]u8comstd.mem.span: Para trabalhar com strings vindas de APIs C,std.mem.span(ptr)converte para[]u8de forma segura ao encontrar o terminador.
Armadilhas Comuns
- Dangling pointer: Retornar ponteiro para variável local causa undefined behavior. Use alocação no heap ou retorne por valor.
- Confundir
*Tcom[]T:*Taponta para um item;[]Té um slice com tamanho. Usar o tipo errado causa erros de compilação. - Many-pointer sem limites:
[*]Tnão tem.len. Não é possível iterar comforsem primeiro convertê-lo para slice. - Ponteiro nulo:
*Tnunca pode ser nulo. Use?*Tse precisar representar nulidade. - Alinhamento: Ponteiros carregam informação de alinhamento. Casts entre tipos com alinhamentos diferentes exigem
@alignCast.
Termos Relacionados
- Slice — Referência com tamanho conhecido
- Sentinel — Ponteiros e slices terminados por sentinela
- usize — Tipo usado para tamanho e indexação
- Dangling Pointer — Ponteiro para memória inválida
- Stack vs Heap — Onde os dados apontados vivem