Memory corruption in slice initialization (probably)

Hi all,

in the process of learning Zig, I’m trying to port a little C project that involves reflection. In C I had to do a lot of macro trickery and I did not enjoy the experience, so I thought this was a good opportunity for Zig.

I’m currently stuck and I don’t have the slightest idea of what I’m doing wrong. The error happens at runtime during schema.dump. I have a memory corruption issue while printing the second field.

const std = @import("std");
const stdout = std.io.getStdOut().writer();

const Field = struct {
    name: []const u8,
    description: []const u8 = "No description provided",
    //default: ?Variant = null,
};

const Schema = struct {
    filename: []const u8,
    fields: []const Field,

    pub fn factory(filename: []const u8, comptime settings: anytype) Schema {
        const fields = init: {
            var rows: [settings.len]Field = undefined;
            inline for (&rows, settings) |*row, setting| {
                row.* = Field{ .name = setting[0], .description = setting[1] };
            }
            break :init rows;
        };
        // This works fine:
        //const fields = .{
        //    Field{ .name = settings[0][0], .description = settings[0][1] },
        //    Field{ .name = settings[1][0], .description = settings[1][1] },
        //    Field{ .name = settings[2][0], .description = settings[2][1] },
        //    Field{ .name = settings[3][0], .description = settings[3][1] },
        //    Field{ .name = settings[4][0], .description = settings[4][1] },
        //};
        return Schema{
            .filename = filename,
            .fields = &fields,
        };
    }

    fn dump(self: Schema, writer: anytype) !void {
        for (self.fields) |field| {
            try writer.print(".{{ \"{[name]s}\", \"{[description]s}\" }},\n", field);
        }
    }
};

pub fn main() !void {
    // `settings` is always comptime known
    const settings = .{
        .{ "setting1", "A boolean", bool, true },
        .{ "setting2", "An int", i32, -123 },
        .{ "setting3", "An unsigned", u32, 123 },
        .{ "setting4", "A float", f64, -1.23 },
        .{ "setting5", "A string", [100]u8, "abc" },
    };
    const schema = Schema.factory("testfile", settings);
    try schema.dump(stdout);
}

You need a generic type here:

const std = @import("std");

const Field = struct {
    name: []const u8,
    description: []const u8 = "No description provided",
    //default: ?Variant = null,
};

fn Schema(comptime settings: anytype) type {
    return struct {
        const Self = @This();

        filename: []const u8,
        fields: [settings.len]Field,

        pub fn init(filename: []const u8) Self {
            const fields = init: {
                var rows: [settings.len]Field = undefined;
                inline for (&rows, settings) |*row, setting| {
                    row.* = .{ .name = setting[0], .description = setting[1] };
                }
                break :init rows;
            };

            return .{
                .filename = filename,
                .fields = fields,
            };
        }

        fn dump(self: Self, writer: anytype) !void {
            for (self.fields) |field| {
                try writer.print(".{{ \"{[name]s}\", \"{[description]s}\" }},\n", field);
            }
        }
    };
}

pub fn main() !void {
    const settings = .{
        .{ "setting1", "A boolean", bool, true },
        .{ "setting2", "An int", i32, -123 },
        .{ "setting3", "An unsigned", u32, 123 },
        .{ "setting4", "A float", f64, -1.23 },
        .{ "setting5", "A string", [100]u8, "abc" },
    };
    const stdout = std.io.getStdOut().writer();

    const schema = Schema(settings).init("testfile");
    try schema.dump(stdout);
}

Sidenote, it’s better Zig style to avoid:

  • Providing type names when they’re inferred
  • Using OOP terms like “factory”
  • Having global IO state

Also, the reason it didn’t work in your case is because fields is a local function variable, so you can’t return a pointer to it. However, it worked in the commented-out case because that tuple has a static lifetime since it only contains comptime-known data, so you can return a pointer to it (see Diving deep into anonymous struct literals).

1 Like

Complementing what @tensorush said, you can put a comptime here to make it static:

3 Likes

After the last two comments, everything is now clear! Thank you!

My main error was assuming fields was comptime-known in the original code, hence surviving the function scope.

1 Like