Comptime struct initialization

I might be missing something simple, but I’ve struggled to no avail.

I want a struct that contains an array of comptime-known size (provided by calling code). I could do this like so:

fn Foo(comptime size: usize) type {
	return struct {
		bar: [size]u8,
	};
}

But, using this might be a little more awkward than some of the simpler cases I’ve seen. One might want to:

var f = Foo(5) {
	.bar = [_]u8{0} ** 5,
};

A little unDRY, but more importantly, if I wanted to provide some more useful initialization, I might prefer the user have an experience like:

var f = Foo(5); // or create_foo(5); -- anything would do

Which resulted in a bar initialization including setting bar[0] = 100, bar[1] = yadda… and so on. I can’t get wrapped around how to do this because something like:

fn create_foo(comptime size: usize) FooType {
	var result = Foo(size);
	result.f[size-1] = 7;
	return result;
}

Would need FooType to be defined, but my Foo requires a comptime parameter, so there’s no normal “const Foo = struct { …” Of course I could ask my caller to do something like:

init_foo(Foo(5), 5);

And make:

fn init_foo(comptime T: type, size: usize) T {
	var result = T {
		.bar = [_]u8{0} ** 5,
	}
	result.bar[0] = 17;
	return result;
}

but this also feels contrived and unDRY. I could break init into two steps and require the calling code to:

f = Foo(5);
f.init();

But I don’t love that, and my real code includes some const arrays in the struct which can’t be modified after the instance is forged, anyway, so two-stepping isn’t an option. How do I achieve calling code that looks like this:

f = create_foo(5);

and results in a struct of my definition, which contains an array stack-sized to 5, and also (preferably) initialized beyond? Thank you in advance for any hints you have!

I think this is the way most Zig user would do something like this:

fn Foo(comptime size: usize) type {
    return struct {
        bar: [size]u8,

        const FooType = @This();

        fn init() FooType {
            var result: FooType = .{ .bar = @splat(0) };
            result.bar[size - 1] = 7;
            return result;
        }
    };
}

const foo: Foo(5) = .init();

But you can also do:

fn Foo(comptime size: usize) type {
    return struct {
        bar: [size]u8,
    };
}

fn create_foo(comptime size: usize) Foo(size) {
    var result: Foo(size) = .{ .bar = @splat(0) };
    result.bar[size - 1] = 7;
    return result;
}

const foo = create_foo(5);
7 Likes

Here are a few more ways to do it:

fn Foo(comptime size: usize) type {
    return struct {
        data: [size]u8 = @splat(0),
        // Or if you are using Zig < 0.14.0
        data: [size]u8 = [_]u8{0} ** size,
    };
}
// Use it like this:
const foo: Foo(12) = .{};
const foo1 = Foo(12){};

Or if you are using Zig >= 0.14.0 you can use Decl Literals

fn Foo(comptime size: usize) type {
    return struct {
        data: [size]u8,

        const Self = @This();

        pub const default = Self{
            .data = @splat(0),
        };
    };
}

// Use it like this
var foo: Foo(12) = .default;
4 Likes

Wow, this is great, thank you. I see what I was previously blind to – since Foo(5) returns a TYPE, it takes its place after the colon, before the equal sign for instance initialization. Don’t know why this was a hangup - makes perfect sense now, and opens all the doors. Thank you rpkak and all others who replied.

3 Likes