如何动态导入 Zig 模块?

8

我正在使用 zig 0.7.0.,尝试从数组中导入一组zig源文件。 每个源文件都有一个名为main的函数(其返回类型为!void),我希望能够调用每个函数。 数组module_names在编译时已知。

这是我尝试的方法:

const std = @import("std");
const log = std.log;

const module_names = [_][]const u8{
    "01.zig", "02.zig", "03.zig", "04.zig", "05.zig",
};

pub fn main() void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    for (module_names) |module_name, i| {
        const module = @import(module_name); // this fails
        log.info("i {}", .{i});
        try module.main();
    }
}

即使在编译时知道数组,@import(module_name) 也会给我出现这个错误:
./src/main.zig:13:32: error: unable to evaluate constant expression
        const module = @import(module_name);
                               ^
./src/main.zig:13:24: note: referenced here
        const module = @import(module_name);

如果数组是在运行时动态生成并且只在运行时才知道,我可以理解这个错误,但是这里的 module_names 数组在编译时已知。所以我有点困惑...

另外,我还尝试将整个 main 体包装在一个 comptime 块中:

pub fn main() void {
    comptime {
        var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
        defer arena.deinit();

        for (module_names) |module_name, i| {
            const module = @import(module_name); // no errors here
            log.info("i {}", .{i});
            try module.main();
        }
    }
}

在这里,@import(module_name) 没有出现错误,但是 log.info 出现了另一个错误:

/home/jack/.zig/lib/zig/std/mutex.zig:59:87: error: unable to evaluate constant expression
            if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null)
                                                                                      ^
/home/jack/.zig/lib/zig/std/mutex.zig:65:35: note: called from here
            return self.tryAcquire() orelse {
                                  ^
/home/jack/.zig/lib/zig/std/log.zig:145:60: note: called from here
            const held = std.debug.getStderrMutex().acquire();
                                                           ^
/home/jack/.zig/lib/zig/std/log.zig:222:16: note: called from here
            log(.info, scope, format, args);
               ^
./src/main.zig:26:21: note: called from here
            log.info("i {}", .{i});

在 Zig 中是否支持这种动态导入方式?

2个回答

5
截至Zig 0.8.0@import 的操作数需要是字符串字面量
Zig编译器希望知道所有可能导入的文件,以便在启动编译过程时能够迅速找到它们并编译它们。语言的设计受到限制,使得快速编译器可以存在。
那么我们该怎么办呢?我认为这样可以以等效的方式完成任务:
const std = @import("std");
const log = std.log;

const modules = struct {
    pub const module_01 = @import("01.zig");
    pub const module_02 = @import("02.zig");
    pub const module_03 = @import("03.zig");
    pub const module_04 = @import("04.zig");
    pub const module_05 = @import("05.zig");
};

pub fn main() void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    inline for (@typeInfo(modules).Struct.decls) |decl, i| {
        const module = @field(modules, decl.name);
        log.info("i {d}", .{i});
        try module.main();
    }
}

这里的妙处在于,编译器能够急切地获取这5个文件并启动编译过程,甚至在运行编译时代码确定哪个文件实际被导入之前。双赢。


2
我认为你需要的是一种导入方式,我的理解是@import将一个zig源文件转换为结构类型。实际上,这似乎是可以在运行时完成的(尽管不能使用@import,因为它需要一个comptime参数(这是你问题的重点)。
你的第一个示例失败的原因是你传递的参数module_namecomptime时是未知的,直到程序运行时才会执行你的for循环。
你解决问题的本能是正确的,让循环在编译时评估(特别是捕获值和迭代器);我认为有两件事情可以解决它。
像你所做的那样将循环包装在一个comptime块中将起作用,但你需要考虑在编译时评估表达式的确切含义,以及它是否有意义。我认为log的实现将阻止你在编译时记录日志,因此你需要在循环内部收集你感兴趣的值,并在comptime块之外记录它们。
另一种解决方法是通过使用内联for来展开循环,强制评估循环的捕获值在编译时进行评估。
pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    inline for (module_names) |module_name, i| {
        const module = @import(module_name);
        log.info("i {}", .{i});
        try module.main();
    }
}

免责声明:可能有更好的方法来完成此操作,我对这种语言相对较新 =D

1
如果您需要在循环中执行运行时操作,那么“inline for”是正确的方法。 - pfg
是的,看起来“inline for”是在这里使用的正确结构。我还尝试通过在“comptime”块中附加“module_name”来构建一个“ArrayList”,但那行不通。我也意识到我的问题标题有点误导人:由于我在编译时知道模块,因此根本不会动态导入它们。我仍然不知道zig是否支持实际的动态导入(即运行时导入)。 - jackdbd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接