Walker.next() Entries as Struct Fields in an ArrayList

I’m hoping someone can please tell me why this behavior is happening and how to work with it / around it. I’m just trying to get the names and paths of files and directories out of a readDirectory() function. (Zig 0.14.1)

    var dir = try std.fs.Dir.openDir (std.fs.cwd(), path, .{ .iterate = true });
    var walker = try dir.walk (gpa);

    while (try walker.next()) |entry| {
        if (!std.mem.containsAtLeast (u8, entry.path, 1, seperator)) { // don't report contents of subdirectories
            stats = try std.fs.Dir.statFile (dir, entry.path);
            try directories.append (DirEntry {
                .name = entry.basename,
                .path = entry.path,
                .kind = entry.kind,
                .size = stats.size,
                .mode = stats.mode,
                .ctime = stats.ctime,
                .mtime = stats.mtime,
                .atime = stats.atime
            });
        }
    }
    walker.deinit();
    dir.close();

Everything works here except .name and .path. These are the only slices involved ([]const u8). According to docs, walker.next() should be returning [:0]const u8’s and those should be automatically coerced. When I debug print entry.basename above the .append operation, it comes out fine. When trying to get the data out of the directories ArrayList, .name and .path will have the right string length, but every character in the string is character 170 (when printed as {d}).

I can force it to work with .name = try std.fmt.allocPrint (gpa, "{s}", .{ entry.basename });, but that allocates memory for itself and I haven’t been able to figure out how to appropriately free it in this situation or give it away to the directories ArrayList so it can be freed with that. (.toOwnedSlice() on the whole ArrayList doesn’t do it.) Giving the slice completely to the ArrayList without allocating third-party memory for it would be perfect. This is being called and carefully managed from within a gui loop, so I’m a little boxed-in on opportunities to free awkwardly allocated memory.

Hello!

Each time you do walker.next() the data from the last iteration referenced by .basename and .path becomes invalid, that’s why you have to allocate them with allocPrint(), or gpa.dupe().

To later free that data you could use something like

for (directories.items) |item| {
    gpa.free(item.name);
    gpa.free(item.path); 
}

Writing that from my phone, sorry for the bad formatting

1 Like

Hello, back! The directories ArrayList is already getting freed with directories.deinit() elsewhere. When I use std.fmt.allocPrint on entries.name or entries.path, the leak-checker tells me that I have a memory leak and it points to the allocPrint() line. It seems that the allocPrint() function used in this way will allocate lots of little slices and pass references to them to the DirEntry fields, but will not give up ownership of them. I could be wrong about that, but that looks to be what’s happening. They don’t get freed by directories.deinit().

directories.deinit() will only deallocate the slice that references the heap memory allocated with allocPrint(), that’s why you have loop over the items of directories freeing .name and .path.

Where you have defer directories.deinit() you can try to change it to:

defer {
    for (directories.items) |item| {
        gpa.free(item.name);
        gpa.free(item.path); 
    }

    directories.deinit();
}
2 Likes

Thanks for the input. I’ll try adding that.

That took care of the memory leak. Looks like I still need to get used to Zig’s ArrayList. Thanks for the tip!

1 Like

BTW the 170 is hex AA, which in debug mode and with the Debug allocator is used to mark uninitialized memory (I don’t know the exact conditions).
Always keep in mind that a slice is only a pointer plus length, it doesn’t say anything about ownership or lifetime.

1 Like

170/0xAA comes from undefined in debug mode, it’s explicitly not part of the language specification rather an implementation detail to make undefined memory stand out when debugging.

DebugAllocator and many std data structures make use of the above behaviour by writing undefined to pointers. That also makes it easier for static analysis tools to track.

1 Like