Understanding how to properly append to an ArrayList

Hello everyone,

JS/TS and Golang developer here

I’m learning zig and while making some basic code, i struggle to understand why it does what it does.

In a function, I have a origin parameter which is a [] const u8, I wanted to loop over a directory to list and gather the path of all the files.

Example: I give ./folder and should see ./folder/file1.png , ./folder/file2.png and so on

Pretty simple


    const dir = try std.fs.cwd().openDir(origin, std.fs.Dir.OpenDirOptions{
        .iterate = true
    });
    
    var files = std.ArrayList([]const u8).init(std.heap.page_allocator);
    
    var it = dir.iterate();
    
    var buffer: [256]u8 = undefined;

    while (try it.next()) |item| {
        switch (item.kind) {
            std.fs.Dir.Entry.Kind.File => {
                var cmd = std.fmt.bufPrint(&buffer, "{s}/{s}", .{ origin, item.name }) catch "error";
                std.debug.print("item.name: {s}\n", .{ cmd });
                try files.append(cmd);
            },
            else => std.debug.print("{s}: {s}\n", .{ item.kind, item.name }),
        }
    }

    std.debug.print("data {s}\n", .{ files.items });

The output is disturbing

The print of the files during the loop are ok and exactly what I was searching for

BUT the print of the items in the list show the same ./sprite9.png and some ./sprite9.pngg which show it “replaced” the string of another like swapping ./sprite10.png but with the latest string ./sprite9.png keeping the last “g”

I have no idea what it does this

I can’t wait to have more docs and more examples to understand how to code correctly in zig, it’s a fascinating language

Thanks for taking the time to read my post

item.name: ./sprite1.png
item.name: ./sprite10.png
item.name: ./sprite11.png
item.name: ./sprite12.png
item.name: ./sprite13.png
item.name: ./sprite14.png
item.name: ./sprite15.png
item.name: ./sprite16.png
item.name: ./sprite17.png
item.name: ./sprite18.png
item.name: ./sprite19.png
item.name: ./sprite2.png
item.name: ./sprite20.png
item.name: ./sprite21.png
item.name: ./sprite22.png
item.name: ./sprite23.png
item.name: ./sprite24.png
item.name: ./sprite25.png
item.name: ./sprite3.png
item.name: ./sprite4.png
item.name: ./sprite5.png
item.name: ./sprite6.png
item.name: ./sprite7.png
item.name: ./sprite8.png
item.name: ./sprite9.png
data { ./sprite9.png, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.png, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.pngg, ./sprite9.png, ./sprite9.png, ./sprite9.png, ./sprite9.png, ./sprite9.png, ./sprite9.png, ./sprite9.png }

[256]u8 is just a piece of memory on the stack and []const u8 is but a cleverly disguised pointer to a memory location + the length of the slice.

In your loop, you’re always using the same memory location for all your formatting and appending operations, resulting in an std.ArrayList() full of the same pointers:

$ cat demo.zig
const std = @import("std");

pub fn main() !void {
    var buffer: [256]u8 = undefined;
    std.debug.print("buffer:  {*}\n", .{&buffer[0]});

    var files = std.ArrayList([]const u8).init(std.heap.page_allocator);
    defer files.deinit();

    var i: usize = 0;
    while (i < 10) : (i += 1) {
        var cmd = try std.fmt.bufPrint(&buffer, "thing{d}", .{i});
        try files.append(cmd);
    }

    for (files.items) |f, fi| {
        std.debug.print("file #{d}: {*}\n", .{ fi, f.ptr });
    }
}

$ zig build-exe demo.zig && ./demo
buffer:  u8@7fffb37d75de
file #0: u8@7fffb37d75de
file #1: u8@7fffb37d75de
file #2: u8@7fffb37d75de
file #3: u8@7fffb37d75de
file #4: u8@7fffb37d75de
file #5: u8@7fffb37d75de
file #6: u8@7fffb37d75de
file #7: u8@7fffb37d75de
file #8: u8@7fffb37d75de
file #9: u8@7fffb37d75de

Instead, you should allocate new memory for each item and deallocate it once you’re done, e.g. for my simplified example above:

$ cat demo.zig
const std = @import("std");

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    var files = std.ArrayList([]const u8).init(allocator);
    defer {
        for (files.items) |f| allocator.free(f);
        files.deinit();
    }

    var i: usize = 0;
    while (i < 10) : (i += 1) {
        var cmd = try std.fmt.allocPrint(allocator, "thing{d}", .{i});
        errdefer allocator.free(cmd);
        try files.append(cmd);
    }

    for (files.items) |f, fi| {
        std.debug.print("file #{d}: {s} {*}\n", .{ fi, f, f.ptr });
    }
}

$ zig build-exe demo.zig && ./demo
file #0: thing0 u8@7fb2cfce5000
file #1: thing1 u8@7fb2cfce7000
file #2: thing2 u8@7fb2cfce8000
file #3: thing3 u8@7fb2cfce9000
file #4: thing4 u8@7fb2cfcea000
file #5: thing5 u8@7fb2cfceb000
file #6: thing6 u8@7fb2cfcec000
file #7: thing7 u8@7fb2cfced000
file #8: thing8 u8@7fb2cfcee000
file #9: thing9 u8@7fb2cfcef000

Now as you can see the strings are formatted correctly, they’re preserved after the main loop is over, and they live at different memory addresses.

5 Likes

Ooooh

First of all, thank you for taking the time to answer with examples of code

Yup I really need to learn how to use those allocators and memory usage/allocation

Good to know about the allocPrint, makes things easier

It indeed made it work

Thanks :slight_smile: