Need help understanding memory leak detected in test

I’m working on the Diamond exercise on Exercism.
The function I implement looks like the following:

pub fn rows(allocator: mem.Allocator, letter: u8) mem.Allocator.Error![][]u8 {
    ...
    const result = try allocator.alloc([]u8, size);
    for (0..size) |i| {
        const row: []u8 = try allocator.alloc(u8, size);
        result[i] = row;
        ...
    }
    return result;    
}

And the test file given by Exercism test it like this:

fn free(slices: [][]u8) void {
    for (slices) |slice| {
        testing.allocator.free(slice);
    }
    testing.allocator.free(slices);
}

fn testRows(allocator: std.mem.Allocator, expected: []const []const u8, letter: u8) !void {
    const actual = try rows(allocator, letter);
    defer free(actual);
    try testing.expectEqual(expected.len, actual.len);
    for (0..expected.len) |i| {
        try testing.expectEqualStrings(expected[i], actual[i]);
    }
}

Since the 2D array I allocate memory for in the rows function is returned to the caller, and it’s already doing the free , I assume that I don’t need to do anything.
But when I run the test I still get memory leak errors for the line const row: []u8 = try allocator.alloc(u8, size);.

Any idea why this happen?

BTW, the version of Zig used is 0.15.2.

free should take the allocator as a parameter, instead of assuming it uses testing.allocator. I don’t think that’s the cause as it should catch if you use it to free memory it didn’t allocate, but that can be disabled so it’s possible.

I think the more likely problem is exercism is passing an allocator that deliberately fails, and its failing inside rows which does not appear to free if an error occurs. If that is the case it will never get to free, so the testing allocator won’t have the chance to detect that it’s the wrong allocator.

Thanks, you are right.
Just figured out I still need to use errdefer to free resources in case something bad happen.
It’s cumbersome to keep track of what’s been allocated manually, maybe I should just use ArenaAllocator inside.