Strange behavior with std.ArrayList (garbled output)

I’m at a bit of a loss. Trying to implement a function that takes a directory name and returns a list of all files in that directory as std.ArrayList.

So far I’ve come up with the following code:

const std = @import("std");
const DirectoryListing = @import("DirectoryListing");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    const dirname: []const u8 = "/etc";
    const filelist = try getDirListing(allocator, dirname);
    for (filelist.items) |element| {
        std.debug.print("  {s} \n", .{element});
    }
}

fn getDirListing(allocator: std.mem.Allocator, directory: []const u8) !std.ArrayList([]const u8) {
    var dir = try std.fs.cwd().openDir(directory, .{ .iterate = true });
    defer dir.close();
    var dirIterator = dir.iterate();
    var files: std.ArrayList([]const u8) = .empty;
    errdefer files.deinit(allocator);
    while (try dirIterator.next()) |dirContent| {
        if (dirContent.kind == .file) {
            std.debug.print("\n{s}\n", .{dirContent.name});
            files.append(allocator, dirContent.name) catch unreachable;
            for (files.items) |element| {
                std.debug.print("{s}, ", .{element});
            }
        }
    }
    return files;
}

Looping through the items of the returned ArrayList in the main function gets me basically just garbled output (mainly special characters).
Doing the same loop inside the function gives me the correct output but once it’s getting beyond 22 entries it all starts to fall apart and the output gets more and more garbled.

I’m pretty new to zig and would appreciate any suggestions how to fix this.

dirContent.name is a pointer provided by an iterator, it becomes invalid after the iterator performs a next(). You need to copy its content in order to access it long-term. In this case, using an arena is recommended.

Made some modifications based on your coding style. In practice, there may be better styles, such as using ArrayList(u8) as the underlying structure and using indices instead of pointers.

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    const dirname: []const u8 = "/etc";
    var filelist, var arena = try getDirListing(allocator, dirname);
    defer arena.deinit();
    defer filelist.deinit(allocator);
    std.debug.print("\n\nstart:\n", .{});
    for (filelist.items) |element| {
        std.debug.print("  {s} \n", .{element});
    }
}

fn getDirListing(allocator: std.mem.Allocator, directory: []const u8) !struct { std.ArrayList([]const u8), std.heap.ArenaAllocator } {
    var dir = try std.fs.cwd().openDir(directory, .{ .iterate = true });
    defer dir.close();
    var dirIterator = dir.iterate();
    var files: std.ArrayList([]const u8) = .empty;
    errdefer files.deinit(allocator);
    var arena: std.heap.ArenaAllocator = .init(allocator);
    errdefer arena.deinit();
    while (try dirIterator.next()) |dirContent| {
        if (dirContent.kind == .file) {
            const name = try arena.allocator().dupe(u8, dirContent.name);
            std.debug.print("\n{s}\n", .{name});
            files.append(allocator, name) catch unreachable;
            for (files.items) |element| {
                std.debug.print("{s}, ", .{element});
            }
        }
    }
    return .{ files, arena };
}

Since you (OP) mentioned you’re new to Zig, I’ll take the liberty of expanding on this valuable comment a bit more:

Your ArrayList is a list of slices, e.g. pointers to memory with a length. You have no control over what’s contained at the memory address until you take ownership of that block of memory. In your example, the block of memory will initially contain the file you expect in the loop. However, once the loop continues to the next iteration, you no longer have any guarantee what was there. That’s why you need to do something like allocator.dupe(…) or std.fmt.allocPrint(…) to copy the value to a new block of memory you will own.

As to why you should use an arena, it’s so you don’t have to track & free the individual allocations (assuming you don’t need to, of course, which I think is fair given the use-case). Let the arena do the allocations so that it can free everything in bulk when needed.

Don’t forget to deinit the ArrayList in main. Alternatively also init it using the arena.

1 Like