Best way to manage list of strings

I am trying to work with lists of strings, that will be processed, with new lists being generated in every iteration. Here’s what I use to generate the list:

pub fn generate(allocator: std.mem.Allocator, prng: *std.rand.DefaultPrng, stringLength: u16, numStrings: u32) ![]*[]const u8 {
    var stringArray: []*[]const u8 = try allocator.alloc(*[]const u8, numStrings);
    var i: u32 = 0;
    while (i < numStrings) {
        var binaryString = try allocator.create(u8, stringLength);
        var c: u32 = 0;
        while (c < stringLength) : (c += 1) {
            binaryString[c] = prng.random().intRangeAtMost(u8, '0', '1');

        stringArray[i] = &binaryString;
        i = i + 1;
    return stringArray;

This mostly works unless you start processing strings in the array; then it starts to break at random moments (processing involves duplicating the array, making some changes, and placing the result in yet another array of strings).

I guess this implies a memory leak somewhere, and I’m pretty sure I’m getting everything wrong about memory allocation. I’d be grateful for any explanation.

1 Like

Hi, welcome to the forum!
Here’s how I’d do it:

const std = @import("std");

pub fn generate(allocator: std.mem.Allocator, random: std.rand.Random, string_len: u16, num_strings: u32) ![][]const u8 {
    var strings = try allocator.alloc([]u8, num_strings);
    for (0..num_strings) |i| {
        strings[i] = try allocator.alloc(u8, string_len);
        for (0..string_len) |j| {
            strings[i][j] = random.intRangeAtMost(u8, '0', '1');
    return strings;

test generate {
    const allocator = std.testing.allocator;

    var prng = std.rand.DefaultPrng.init(blk: {
        var seed: u64 = undefined;
        try std.os.getrandom(std.mem.asBytes(&seed));
        break :blk seed;

    const strings = try generate(allocator, prng.random(), 5, 10);

    for (strings, 0..) |string, i| {
        std.debug.print("String #{d}: {s}\n", .{ i, string });

    for (strings) |string| {;

Here’s a working program. See below for some notes.

const std = @import("std");

pub fn generate(
    allocator: std.mem.Allocator,
    prng: *std.rand.DefaultPrng,
    stringLength: u16,
    numStrings: u32,
) ![][]u8 {
    const strings: [][]u8 = try allocator.alloc([]u8, numStrings);

    // An error may occur at any point, so we make sure to
    // free what we have up till that point.
    var allocated: usize = 0;
    errdefer {
        for (0..allocated) |i|[i]);;

    // The *str capture gives you mutable access.
    for (strings) |*str_ptr| {
        str_ptr.* = try allocator.alloc(u8, stringLength);
        allocated += 1;

        // Same here, *b can be mutated directly.
        for (str_ptr.*) |*byte_ptr| {
            byte_ptr.* = prng.random().intRangeAtMost(u8, '0', '1');

    return strings;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var prng = std.rand.DefaultPrng.init(0);

    const strs = try generate(allocator, &prng, 8, 3);
    defer {
        for (strs) |str|;;
    for (strs) |str| std.debug.print("{s}\n", .{str});
  • You can use the * in a for loop capture to get a pointer to the item and thus be able to modify it directly.
  • Allocating with alloc returns a slice of the type you specify, so for u8 it’s []u8 and not []const u8. So I set the return type to [][]u8 accordingly.
  • Every time you alloc or create, you have to think about possible errors occurring mid-flight, so you have to devise an errdefer strategy to free the already allocated items.
1 Like

Thanks a lot for the answer. I see you’re using snake_case where I used CamelCase. Would that be the “ziggish” way?

1 Like

You’re welcome! And yeah, it is actually! I’m so used to it that can’t help maintaining it everywhere :sweat_smile: Here’s the “Names” section of the official style guide.


It’s also worth pointing out that if you follow the style guide, you’ll get better lsp support. ZLS current highlights things based on that convention :slight_smile: