fn init(allocator: std.mem.Allocator, path: []const u8) !Store.Iterator {
var dir = try std.fs.openDirAbsolute(path, .{ .iterate = true });
var iter = Iterator{
.walker = try dir.walk(allocator),
.dir = dir,
};
return Store.Iterator.init(&iter); // <- problem!
}
You are returning something that contains a reference to a variable on the init
stack frame, which will no longer be valid after init
returns. It might work the first iteration, but the location in memory it points to will be overwritten by garbage the next time the stack grows to that point.
Instead of having FileSystemStore.Iterator.init
directly return a Store.Iterator
, consider changing your design so it returns a FileSystemStore.Iterator
, and then expose some kind of asStoreIterator
method that takes a reference to FileSystemStore.Iterator
(which will have a lifetime determined by the caller) and returns the type-erased Store.Iterator
. This is similar to the pattern interfaces like Allocator
and Reader
/Writer
use in the standard library:
// this is the impl-specific state, which lives on this stack frame
var gpa_state: std.heap.GeneralPurposeAllocator(.{}) = .init;
defer std.debug.assert(gpa_state.deinit() == .ok);
// this is the type-erased context pointer and function pointer(s)
const allocator = gpa_state.allocator();