Capturar Ponteiro em For Loop — Como Resolver em Zig
O Que Este Erro Significa
Capturar um ponteiro para a variável de iteração de um for loop em Zig pode levar a comportamento inesperado. A variável de captura no for é uma cópia local que muda a cada iteração. Se você salvar um ponteiro para ela, o ponteiro fica apontando para memória que será reutilizada ou invalidada, resultando em valores incorretos ou comportamento indefinido.
Este é um erro sutil que ocorre tanto em Zig quanto em linguagens como Go e C, e é especialmente perigoso porque pode parecer funcionar em alguns casos por coincidência.
Causas Comuns
1. Guardar Ponteiro para Variável de Captura
const std = @import("std");
pub fn main() void {
const items = [_]u32{ 10, 20, 30, 40 };
var ponteiros: [4]*const u32 = undefined;
for (items, 0..) |item, i| {
ponteiros[i] = &item;
// ERRO: &item aponta para a CÓPIA local, não para o array
// Todos os ponteiros apontam para o mesmo endereço temporário
}
// Todos os ponteiros mostram o ÚLTIMO valor ou lixo
for (ponteiros) |ptr| {
std.debug.print("{}\n", .{ptr.*});
}
}
2. Enviar Ponteiro de Captura para Outra Função
const std = @import("std");
var referencia_salva: ?*const u32 = null;
fn guardar(ptr: *const u32) void {
referencia_salva = ptr;
}
pub fn main() void {
const items = [_]u32{ 1, 2, 3 };
for (items) |item| {
guardar(&item); // Ponteiro para cópia temporária
}
// referencia_salva aponta para memória inválida
if (referencia_salva) |ptr| {
std.debug.print("valor: {}\n", .{ptr.*}); // Lixo ou crash
}
}
3. Closure Capturando Variável de Loop
const std = @import("std");
pub fn main() void {
const items = [_]u32{ 10, 20, 30 };
var callbacks: [3]*const fn () void = undefined;
for (items, 0..) |item, i| {
_ = item;
// Tentativa de capturar item em closure
callbacks[i] = struct {
fn call() void {
// item não é acessível aqui de forma segura
}
}.call;
}
}
4. Modificar Array via Ponteiro de Captura (Valor)
pub fn main() void {
var numeros = [_]u32{ 1, 2, 3, 4, 5 };
for (numeros) |item| {
_ = item;
// item é uma CÓPIA — modificar item não altera numeros
// NÃO pode fazer: item = item * 2;
}
// numeros permanece inalterado: [1, 2, 3, 4, 5]
}
Como Corrigir
Solucao 1: Usar Ponteiro para o Array Original
const std = @import("std");
pub fn main() void {
const items = [_]u32{ 10, 20, 30, 40 };
var ponteiros: [4]*const u32 = undefined;
for (&items, 0..) |*item, i| {
// ERRADO: capturar ponteiro de iteração
// CORRETO: usar índice para apontar para o array original
_ = item;
ponteiros[i] = &items[i]; // Ponteiro para o array real
}
// Agora os ponteiros são válidos
for (ponteiros) |ptr| {
std.debug.print("{}\n", .{ptr.*});
}
}
Solucao 2: Usar Captura por Ponteiro (para Mutação)
const std = @import("std");
pub fn main() void {
var numeros = [_]u32{ 1, 2, 3, 4, 5 };
// Captura por ponteiro — modifica o array diretamente
for (&numeros) |*ptr| {
ptr.* *= 2;
}
// numeros agora é [2, 4, 6, 8, 10]
for (numeros) |n| {
std.debug.print("{} ", .{n});
}
std.debug.print("\n", .{});
}
Solucao 3: Copiar o Valor, Não o Ponteiro
const std = @import("std");
pub fn main() void {
const items = [_]u32{ 10, 20, 30 };
var valores: [3]u32 = undefined;
for (items, 0..) |item, i| {
valores[i] = item; // Copia o VALOR, não o ponteiro
}
// valores contém cópias corretas
for (valores) |v| {
std.debug.print("{} ", .{v});
}
std.debug.print("\n", .{});
}
Solucao 4: Usar Índice ao Invés de Captura
const std = @import("std");
pub fn main() void {
var items = [_]u32{ 1, 2, 3, 4, 5 };
// Usar índice direto para modificar
for (0..items.len) |i| {
items[i] *= 2;
}
for (items) |item| {
std.debug.print("{} ", .{item});
}
std.debug.print("\n", .{});
}
Solucao 5: Armazenar em Estrutura por Valor
const std = @import("std");
const Resultado = struct {
valor: u32,
indice: usize,
};
pub fn main() void {
const items = [_]u32{ 100, 200, 300 };
var resultados: [3]Resultado = undefined;
for (items, 0..) |item, i| {
// Armazena por VALOR, não por ponteiro
resultados[i] = .{
.valor = item,
.indice = i,
};
}
for (resultados) |r| {
std.debug.print("Índice {}: {}\n", .{ r.indice, r.valor });
}
}
Solucao 6: Ponteiro para Slice de ArrayList
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var lista = std.ArrayList(u32).init(allocator);
defer lista.deinit();
try lista.appendSlice(&[_]u32{ 10, 20, 30 });
// Usar slicing para obter ponteiros estáveis
// (cuidado: append pode invalidar o slice)
const slice = lista.items;
for (slice) |*item| {
item.* += 5;
}
for (lista.items) |item| {
std.debug.print("{} ", .{item});
}
std.debug.print("\n", .{});
}
Captura por Valor vs Captura por Ponteiro
var arr = [_]u32{ 1, 2, 3 };
// Captura por VALOR — item é uma cópia (read-only)
for (arr) |item| {
_ = item; // Cópia — modificar não afeta arr
}
// Captura por PONTEIRO — ptr aponta para o elemento real
for (&arr) |*ptr| {
ptr.* += 1; // Modifica arr diretamente
}
Regra de Ouro
- Nunca salve
&itemde umfor— é ponteiro para cópia temporária. - Use
&array[i]se precisa de ponteiro persistente para um elemento. - Use
|*ptr|(captura por ponteiro com&array) se quer modificar in-place. - Copie valores, não ponteiros, quando precisa guardar dados de iterações.
Erros Relacionados
- Use after free — Usar memória após liberação
- Return local pointer — Retornar ponteiro para variável local
- Segmentation fault — Acesso a memória inválida