Making variables comptime known in loop

Hi,

I would like to run multiple files main() function, something like this:

const std = @import("std");

const includes = struct {
    const field1 = @import("foo.zig");
    const field2 = @import("bar.zig");
    const field3 = @import("baz.zig");
};

const Str = []const u8;

const days = 3;

const fields = [days]Str{"field1", "field2", "field3"};
const paths = [days]Str{"foo.txt", "bar.txt", "baz.txt"};

pub fn main() !void {
    for (fields) |field, idx| {
        const file = @embedFile(paths[idx]);
        const result = @field(includes, field).main(file);
        std.debug.print("{}", .{result});
    }
}

I understand that it will not work as both @embedFile() and @field() expects compile time known []const u8.
On the other hand these could be comptime known, an inline for could make this work, but I do not want to call the functions compile time and have no idea how to glue these things together.

So I am just repeating the things in the loop, but I would be curious if this could be solved more elegantly. Thanks,

An inline for does not force any of the code inside its block to be evaluated at comptime, it just generates the same code repeatedly for each iteration - code which can be executed at runtime or at comptime. So the field and idx captures will become comptime-known, but the rest of the code (like the call to the selected main function, and std.debug.print) will be compiled into runtime code.

3 Likes

Oh my God! It have not even crossed my mind, that it could be so simple. This is pretty amazing. Thank You very much!

1 Like

This worked like a charm, until I have run into a strange issue:

I had this file:

const std = @import("std");

const day01a = @import("day01a.zig");
const day01b = @import("day01b.zig");
const day02a = @import("day02a.zig");
const day02b = @import("day02b.zig");
const day03a = @import("day03a.zig");
const day03b = @import("day03b.zig");
const day04a = @import("day04a.zig");
const day04b = @import("day04b.zig");
const day05a = @import("day05a.zig");
const day05b = @import("day05b.zig");
const day06a = @import("day06a.zig");
const day06b = @import("day06b.zig");
const day07a = @import("day07a.zig");
const day07b = @import("day07b.zig");
const day08a = @import("day08a.zig");
const day08b = @import("day08b.zig");
const day09a = @import("day09a.zig");
const day09b = @import("day09b.zig");
const day10a = @import("day10a.zig");
const day10b = @import("day10b.zig");
const day11a = @import("day11a.zig");
const day11b = @import("day11b.zig");
const day12a = @import("day12a.zig");
const day12b = @import("day12b.zig");
const day13a = @import("day13a.zig");
const day13b = @import("day13b.zig");
const day14a = @import("day14a.zig");
const day14b = @import("day14b.zig");
const day15a = @import("day15a.zig");
const day15b = @import("day15b.zig");
const day16a = @import("day16a.zig");
const day16b = @import("day16b.zig");
const day17a = @import("day17a.zig");
const day17b = @import("day17b.zig");
const day18a = @import("day18a.zig");
const day18b = @import("day18b.zig");
const day19a = @import("day19a.zig");
const day19b = @import("day19b.zig");
const day20a = @import("day20a.zig");
const day20b = @import("day20b.zig");
const day21a = @import("day21a.zig");
const day21b = @import("day21b.zig");
const day22a = @import("day22a.zig");
const day22b = @import("day22b.zig");
const day23a = @import("day23a.zig");
const day23b = @import("day23b.zig");
const day24a = @import("day24a.zig");
const day24b = @import("day24b.zig");
const day25a = @import("day25a.zig");

pub fn main() anyerror!void {
    var timer = try std.time.Timer.start();
    _ = try day01a.first();
    _ = try day01b.second();
    _ = try day02a.first();
    _ = try day02b.second();
    _ = try day03a.first();
    _ = try day03b.second();
    _ = try day04a.first();
    _ = try day04b.second();
    _ = try day05a.first();
    _ = try day05b.second();
    _ = try day06a.first();
    _ = try day06b.second();
    _ = try day07a.first();
    _ = try day07b.second();
    _ = try day08a.first();
    _ = try day08b.second();
    _ = try day09a.first();
    _ = try day09b.second();
    _ = try day10a.first();
    _ = try day10b.second();
    _ = try day11a.first();
    _ = try day11b.second();
    _ = try day12a.first();
    _ = try day12b.second();
    _ = try day13a.first();
    _ = try day13b.second();
    _ = try day14a.first();
    _ = try day14b.second();
    _ = try day15a.first();
    _ = try day15b.second();
    _ = try day16a.first();
    _ = try day16b.second();
    _ = try day17a.first();
    _ = try day17b.second();
    _ = try day18a.first();
    _ = try day18b.second();
    _ = try day19a.first();
    _ = try day19b.second();
    _ = try day20a.first();
    _ = try day20b.second();
    _ = try day21a.first();
    _ = try day21b.second();
    _ = try day22a.first();
    _ = try day22b.second();
    _ = try day23a.first();
    _ = try day23b.second();
    _ = try day24a.first();
    _ = try day24b.second();
    _ = try day25a.first();
    const t = timer.lap() / 1_000_000;

    std.debug.print("Total running time: {d}ms\n", .{t});
}

I have turned that one to this:

const std = @import("std");

const aoc2021 = struct {
    const day01a = @import("day01a.zig");
    const day01b = @import("day01b.zig");
    const day02a = @import("day02a.zig");
    const day02b = @import("day02b.zig");
    const day03a = @import("day03a.zig");
    const day03b = @import("day03b.zig");
    const day04a = @import("day04a.zig");
    const day04b = @import("day04b.zig");
    const day05a = @import("day05a.zig");
    const day05b = @import("day05b.zig");
    const day06a = @import("day06a.zig");
    const day06b = @import("day06b.zig");
    const day07a = @import("day07a.zig");
    const day07b = @import("day07b.zig");
    const day08a = @import("day08a.zig");
    const day08b = @import("day08b.zig");
    const day09a = @import("day09a.zig");
    const day09b = @import("day09b.zig");
    const day10a = @import("day10a.zig");
    const day10b = @import("day10b.zig");
    const day11a = @import("day11a.zig");
    const day11b = @import("day11b.zig");
    const day12a = @import("day12a.zig");
    const day12b = @import("day12b.zig");
    const day13a = @import("day13a.zig");
    const day13b = @import("day13b.zig");
    const day14a = @import("day14a.zig");
    const day14b = @import("day14b.zig");
    const day15a = @import("day15a.zig");
    const day15b = @import("day15b.zig");
    const day16a = @import("day16a.zig");
    const day16b = @import("day16b.zig");
    const day17a = @import("day17a.zig");
    const day17b = @import("day17b.zig");
    const day18a = @import("day18a.zig");
    const day18b = @import("day18b.zig");
    const day19a = @import("day19a.zig");
    const day19b = @import("day19b.zig");
    const day20a = @import("day20a.zig");
    const day20b = @import("day20b.zig");
    const day21a = @import("day21a.zig");
    const day21b = @import("day21b.zig");
    const day22a = @import("day22a.zig");
    const day22b = @import("day22b.zig");
    const day23a = @import("day23a.zig");
    const day23b = @import("day23b.zig");
    const day24a = @import("day24a.zig");
    const day24b = @import("day24b.zig");
    const day25a = @import("day25a.zig");
};

const days = 25;

pub fn main() anyerror!void {
    var timer = try std.time.Timer.start();

    comptime var day: u5 = 0;
    inline while (day < days) : (day += 1) {
        comptime var bufa: [6]u8 = undefined;
        comptime var bufb: [6]u8 = undefined;
        const dsa = comptime try std.fmt.bufPrint(&bufa, "day{d:0>2}a", .{day + 1});
        const dsb = comptime try std.fmt.bufPrint(&bufb, "day{d:0>2}b", .{day + 1});
        // @compileLog(dsa);
        // @compileLog(dsb);
        _ = try @field(aoc2021, dsa).first();
        if (day < 24) _ = try @field(aoc2021, dsb).second();
    }

    const t = timer.lap() / std.time.ns_per_ms;

    std.debug.print("Total running time: {d}ms\n", .{t});
}

Running this gives me a compile error:

./src/main.zig:68:13: error: control flow attempts to use compile-time variable at runtime
        _ = try @field(aoc2021, dsa).first();
            ^
./src/main.zig:61:38: note: compile-time variable assigned here
    inline while (day < days) : (day += 1) {
                                     ^

Changing the days constant to 21 or lower compiles fine. I can not figure out why that happens. I have tried using @compileLog() to check the states, but the dsa and dsb values seems fine.

What should I do with this?

Could this be a Zig version issue? I tried to reproduce the problem using a mock struct with frist and second methods and it runs to completion without error. I’m on Zig 0.10.0-dev.1355+8a43d67c3.

Hi,

Thanks for trying it!

I just checked with 0.9.1, 0.10.0-1226 was the original version, I have upgraded to 0.10.0-1738, but all gives the same error message.
I also tried removing ~/.cache/zig and zig-cache too, but nothing has changed.

If You could try the whole thing:
Pijul: voroskoi/aoc2021-zig
Git: https://git.sr.ht/~voroskoi/aoc2021-zig

I just run zig run src/main.zig.

Ok so when I first read your post, the fact that the error message was pointing to the try keyword caught my eye, making me wonder if in some way, the try error handling machinery isn’t happy in a comptime context. Why? I truly have no idea. But when I changed those lines to these:

_ = @field(aoc2021, dsa).first() catch unreachable;                                                                                                                                            
if (day < 24) _ = @field(aoc2021, dsb).second() catch unreachable;

it compiles and runs. I tried doing the catch |err| return err but that produces the same error as before. Which should be expected given that it’s functionally equivalent to using try.

Maybe someone with a deeper level of knowledge in Zig could explain why this is so.

PS- Output on a Mac Mini M1: Total running time: 4928ms

2 Likes

Oh, Thank You very much! I would have never figured this out :slight_smile:

2 Likes

This issue seems related, for anyone interested:
https://github.com/ziglang/zig/issues/9524
There are also links to other related issues within the discussion under the issue. It should be of note that it has been labelled as a stage1 bug, and not as a proposal.

As I understand it, the current compiler seems to become upset when you try to exit, or jump between, any inline-ed blocks, by any means of control flow (breaking, continuing, returning). So the work around is to use alternative methods like unreachable, which is noreturn (it doesn’t escape the block, and instead just never cedes control flow back to the “caller”).

Otherwise use mutation if you need to return a value in some way (e.g. use a boolean flag to indicate whether the value has been determined, instead of using continue).

1 Like