Return Local Pointer — Como Resolver em Zig
O Que Este Erro Significa
O erro de retornar ponteiro para variável local ocorre quando uma função tenta retornar um ponteiro ou slice que referencia dados na stack da função. Quando a função retorna, sua stack frame é destruída e a memória é reutilizada. O ponteiro retornado se torna um “dangling pointer” — aponta para memória inválida que será sobrescrita pela próxima chamada de função.
Zig detecta este erro em tempo de compilação na maioria dos casos:
error: pointer to local variable 'buffer' returned from function
Ou:
error: function returns address of local variable
Este é um erro clássico em linguagens com gerenciamento manual de memória (C, C++), e Zig o previne staticamente quando possível.
Causas Comuns
1. Retornar Ponteiro para Array Local
fn criarBuffer() *[100]u8 {
var buffer: [100]u8 = undefined;
@memset(&buffer, 0);
return &buffer; // ERRO: buffer está na stack e será destruído
}
2. Retornar Slice de Array Local
fn obterDados() []u8 {
var dados: [50]u8 = undefined;
@memset(&dados, 'A');
return &dados; // ERRO: dados está na stack
}
3. Retornar Ponteiro para Struct Local
const Config = struct {
porta: u16,
host: []const u8,
};
fn criarConfig() *Config {
var config = Config{
.porta = 8080,
.host = "localhost",
};
return &config; // ERRO: config está na stack
}
4. Retornar Ponteiro para Variável de Loop
fn encontrar(arr: []const u32, alvo: u32) ?*const u32 {
for (arr) |item| {
if (item == alvo) {
return &item; // ERRO: item é cópia na stack
}
}
return null;
}
5. Retornar String Formatada em Buffer Local
const std = @import("std");
fn formatarNumero(n: u32) []const u8 {
var buf: [20]u8 = undefined;
const resultado = std.fmt.bufPrint(&buf, "{d}", .{n}) catch return "";
return resultado; // ERRO: buf está na stack
}
Como Corrigir
Solucao 1: Retornar por Valor (Cópia)
A solução mais simples — retorne a struct ou array por valor, não por ponteiro:
const Config = struct {
porta: u16,
host: []const u8,
};
fn criarConfig() Config {
return .{
.porta = 8080,
.host = "localhost",
};
}
pub fn main() void {
const config = criarConfig(); // Cópia na stack de main
_ = config;
}
Solucao 2: Alocar no Heap
const std = @import("std");
fn criarBuffer(allocator: std.mem.Allocator) ![]u8 {
const buffer = try allocator.alloc(u8, 100);
@memset(buffer, 0);
return buffer; // OK: memória está no heap
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const buf = try criarBuffer(allocator);
defer allocator.free(buf);
buf[0] = 42;
}
Solucao 3: Receber Buffer como Parâmetro
Padrão idiomático em Zig — quem chama fornece o buffer:
const std = @import("std");
fn formatarNumero(n: u32, buf: []u8) ![]u8 {
return std.fmt.bufPrint(buf, "{d}", .{n});
}
pub fn main() !void {
var buf: [20]u8 = undefined;
const resultado = try formatarNumero(42, &buf);
std.debug.print("Formatado: {s}\n", .{resultado});
// buf está na stack de main — válido durante todo o escopo
}
Solucao 4: Usar Ponteiro para o Array Original
fn encontrar(arr: []const u32, alvo: u32) ?*const u32 {
for (arr, 0..) |item, i| {
if (item == alvo) {
return &arr[i]; // OK: aponta para o array original, não para cópia
}
}
return null;
}
pub fn main() void {
const dados = [_]u32{ 10, 20, 30 };
if (encontrar(&dados, 20)) |ptr| {
_ = ptr.*;
}
}
Solucao 5: Retornar String Literal (Comptime)
Strings literais são alocadas no segmento de dados do programa, não na stack:
fn obterMensagem(codigo: u32) []const u8 {
return switch (codigo) {
0 => "Sucesso", // OK: string literal está no binário
1 => "Erro de entrada", // OK
2 => "Erro de conexão", // OK
else => "Erro desconhecido", // OK
};
}
pub fn main() void {
const msg = obterMensagem(1);
_ = msg; // Válido — string literal vive para sempre
}
Solucao 6: Usar allocator.create para Structs no Heap
const std = @import("std");
const Config = struct {
porta: u16,
host: []const u8,
};
fn criarConfig(allocator: std.mem.Allocator) !*Config {
const config = try allocator.create(Config);
config.* = .{
.porta = 8080,
.host = "localhost",
};
return config; // OK: no heap
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const config = try criarConfig(allocator);
defer allocator.destroy(config);
std.debug.print("Porta: {}\n", .{config.porta});
}
Quando É Seguro Retornar Ponteiro
// SEGURO: ponteiro para parâmetro (vida útil >= chamador)
fn primeiro(slice: []u32) *u32 {
return &slice[0]; // OK: slice existe no chamador
}
// SEGURO: ponteiro para global
var global: u32 = 0;
fn obterGlobal() *u32 {
return &global; // OK: variável global vive para sempre
}
// SEGURO: ponteiro para memória alocada no heap
fn alocar(allocator: std.mem.Allocator) !*u32 {
const ptr = try allocator.create(u32);
ptr.* = 42;
return ptr; // OK: heap outlives a função
}
Padrões Idiomáticos em Zig
| Situação | Solução Recomendada |
|---|---|
| Retornar struct pequena | Retornar por valor |
| Retornar string formatada | Receber buffer como parâmetro |
| Retornar dados grandes | Alocar no heap (receber allocator) |
| Retornar texto fixo | Usar string literal |
| Retornar elemento de coleção | Retornar ponteiro para o original |
Comparação com C
// C — PERIGO: compila sem aviso, mas é bug
char* criar_mensagem() {
char buf[100];
sprintf(buf, "Olá");
return buf; // Dangling pointer! Nenhum aviso.
}
// Zig — SEGURO: erro de compilação
fn criarMensagem() []u8 {
var buf: [100]u8 = undefined;
return &buf; // ERRO DE COMPILAÇÃO: ponteiro para local
}
Zig previne este bug antes de executar o programa.
Erros Relacionados
- Use after free — Usar memória após liberação
- Capturar ponteiro em for — Ponteiro para variável de iteração
- Segmentation fault — Acesso a memória inválida
- Stack overflow — Estouro da pilha