Search code examples
zig

How to create 2d arrays of containers in zig?


I'm trying to alloc 2d arrays of HashMap(u32, u1) in Zig:

fn alloc2d(comptime t: type, m: u32, n: u32, allocator: *Allocator) callconv(.Inline) ![][]t {
    const array = try allocator.alloc([]t, m);
    for (array) |_, index| {
        array[index] = try allocator.alloc(t, n);
    }
    return array;
}

fn free2d(comptime t: type, array: [][]t, allocator: *Allocator) callconv(.Inline) void {
    for (array) |_, index| {
        allocator.free(array[index]);
    }
    allocator.free(array);
}

test "Alloc 2D Array" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = &gpa.allocator;
    defer _ = gpa.deinit();

    const HashSet = std.AutoHashMap(u32, u1);
    var array = try alloc2d(*HashSet, 4, 4, allocator);
    defer free2d(*HashSet, array, allocator);

    for (array) |_, i| {
        for (array[i]) |_, j| {
            array[i][j] = &(HashSet.init(allocator));
        }
    }
    defer {
        for (array) |_, i| {
            for (array[i]) |_, j| {
                array[i][j].deinit();
            }
        }
    }
}

However, when I test it, the debugger throw a seg fault.

Can anyone tell me what is happening and how to fix it?

Thanks a lot!


Solution

  • I was having a look over your code and at first glance it seems to do what you're expecting; I wasn't quite sure why you were passing a *HashSet rather than just a HashSet to your functions:

    ...
    
    var array = try alloc2d(HashSet, 4, 4, allocator);
    defer free2d(HashSet, array, allocator);
    
    for (array) |_, i| {
        for (array[i]) |_, j| {
            array[i][j] = HashSet.init(allocator);
        }
    }
    
    ... 
    

    In fact, if you do that, everything works as you'd expect.

    That said, I couldn't see a reason why your version didn't work, so I had a poke at it, and found that what seems to be happening is that every single item in your array is being initialised with the same address, i.e. &(HashSet.init(allocator)) is returning the same address every time. I think this is why the deinit call is segfaulting, the memory is being freed multiple times.

    If you manually initialise every element in the array e.g. [0][0] = (HashSet.init(allocator)...etc everything seems to work. I'm not entirely sure what's going on here, but it might be that there's some kind of compiler optimisation at play, perhaps related to the way generics work. Hopefully someone else will come along with a better answer.

    Slightly unrelated, but a neat feature of Zig, you can iterate over a slice by reference which can sometimes be easier to read:

    for (array) |*outer| {
        for (outer.*) |*item| {
            item.* = <something>
        }
    }