Initialize arrays at comptime

I try to use comptime to initialise some arrays, roughly:

const width = [_]u8{42, 24};
const height = [_]u8{1, 2};
const sizeBits: [2]u8 = undefined;
const spareBits: [2]u8 = undefined;

comptime {
    for (width, height, 0..) |w, h, i| {
        sizeBits[i] = w * h;
        spareBits[i] = (w * h) % 8;
        //...a couple other arrays to calculate at comptime.
    }
}

I somehow thought comptime would allow the setting of an undefined const. Something like a static initializer, But:

cannot assign to constant

When using vars I get: unable to evaluate comptime expression and operation is runtime due to this operand, which I think is pointing to the array index.

For sure I can just use a regular fn init() block, but this feels like a perfect use-case for comptime? What am I missing please.

const myarr = blk: {
  var myarr: [1]u8 = undefined;
  myarr[0] = 1;
  break :blk myarr;
};
3 Likes

I’d do something like this:

const width = [_]u8{ 42, 24 };
const height = [_]u8{ 1, 2 };
const sizeBits: [2]u8 = init: {
    var result: [2]u8 = undefined;
    for (width, height, 0..) |w, h, i| {
        result[i] = w * h;
    }
    break :init result;
};
const spareBits: [2]u8 = init: {
    var result: [2]u8 = undefined;
    for (width, height, 0..) |w, h, i| {
        result[i] = (w * h) % 8;
    }
    break :init result;
};

test "sparebits" {
    for (spareBits, sizeBits) |spare, size| {
        std.debug.print("spare = {}, size = {}\n", .{ spare, size });
    }
}
3 Likes

Consider using tuple destructuring to avoid repeating the loop:

const sizeBits, const spareBits = init: {
    var sizes: [2]u8 = undefined;
    var spares: [2]u8 = undefined;
    for (width, height, 0..) |w, h, i| {
        sizes[i] = w * h;
        spares[i] = (w * h) % 8;
    }
    break :init .{ sizes, spares };
};
7 Likes

once it starts to get complicated, I prefer

fn myArr() [1]u8 {
    var r: [1]u8 = undefined;
    r[0] = 1;
    return r;
}

// If I'm not mistaken explicit comptime isn't required for something this simple.
const myarr = comptime myArr(); 

So this morning I learned about tuples, nice! multiple return values was something I was missing from my Go days :slight_smile:
I was trying to avoid the messy block syntax with the dodgy goto :wink: But in this case it ticks the boxes, comptime, single loop and still maintains const. Thanks!

Edit: I should have tested first… Is this syntax a new feature? Since I get expected ';' after declaration after const sizeBits,

Thanks to everyone else for their input.

1 Like

Ah, that’s my mistake. I misremembered, and destructuring only works at block scope, not at container scope. From master documentation:

A destructuring expression may only appear within a block (i.e. not at container scope). The left hand side of the assignment must consist of a comma separated list, each element of which may be either an lvalue (for instance, an existing var) or a variable declaration:

No worries, I’ll stick with a runtime init() then, but at least I have an excuse to avoid that syntax :wink:

The use of comptime in a context that’s already comptime isn’t allowed.

Anyway, using functions is the standard way of avoid duplication. While the lack of function expression in Zig means more verbosity, it’s still relatively clean:

const width = [_]u8{ 42, 24 };
const height = [_]u8{ 1, 2 };
const sizeBits: [2]u8 = init(width, height, struct {
    fn calc(w: u8, h: u8) u8 {
        return w * h;
    }
});
const spareBits: [2]u8 = init(width, height, struct {
    fn calc(w: u8, h: u8) u8 {
        return (w * h) % 8;
    }
});

fn init(ws: anytype, hs: anytype, ns: type) [ws.len]u8 {
    var rs: [ws.len]u8 = undefined;
    for (ws, hs, 0..) |w, h, i| rs[i] = ns.calc(w, h);
    return rs;
}

Just want to point out that the word context is important here.

A boolean expression, for example, like:

if (some_opt) { ... }

Will or will not happen at comptime based on the comptime knowability of some_opt. We can say

if (comptime some_opt) { ... }

To ensure that it is comptime-knowable, and I like to do this for documentary reasons, not consistently but often.

But this is a function-body thing, which is not a comptime context.

I wish it did. I don’t consider container scope special enough to justify the irregularity of that decision.

Perhaps it’s meant to be more readable by lining up all declarations, but I don’t agree that having an intermediate tuple-containing declaration, which is then manually assigned to sundry identifiers, is more readable than the alternative.

Ah well. It would be one less thing to misremember…

So I came up with a third option… print to console and copy-paste the computed values into the code. I have 7 static arrays for this already, so I dont feel too guilty code-generating a few more, well, maybe a little guilty :wink:

End result, my PinePhone Zig OS now has a couple fonts to play with :slight_smile:

UI rendering next, although I might have to revisit my ‘array performance thread’, since poking this many pixels into my framebuffer array is starting to lag again.