Capturar Ponteiro em For Loop — Como Resolver em Zig

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

  1. Nunca salve &item de um for — é ponteiro para cópia temporária.
  2. Use &array[i] se precisa de ponteiro persistente para um elemento.
  3. Use |*ptr| (captura por ponteiro com &array) se quer modificar in-place.
  4. Copie valores, não ponteiros, quando precisa guardar dados de iterações.

Erros Relacionados

Continue aprendendo Zig

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