简体   繁体   中英

How to import zig modules dynamically?

I'm using zig 0.7.0. and I'm trying to import a list of zig source files from an array. Each source file has a main function (whose return type is !void ) that I would like to call. The array module_names is known at compile time.

Here is what I tried to do:

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();
    }
}

Even if the array is known at compile time, @import(module_name) gives me this error:

./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);

I could understand the error if the array would be dynamically generated and only known at runtime, but here the module_names array is known at compile time. So I am a bit confused...

Alternatively, I also tried to wrap the entire main body in a comptime block:

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();
        }
    }
}

Here @import(module_name) gives me no errors, but the log.info fails with this other error:

/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});

Is this kind of dynamic import possible in zig?

I think the kind of import you're after is possible, my understanding is that @import is taking a zig source file and turning it into a struct type. It actually seems like something that could even be done at runtime (although not using @import , which wants a comptime parameter (this is the important part for your problem).

The reason why your first example fails is that the argument you're passing, module_name isn't known at comptime , your for loop won't execute until your program runs.

Your instinct to solve the problem is correct, get the loop to evaluate at compile time (specifically the capture value and iterator); I think there are two things you could do fix it.

Wrapping the loop in a comptime block as you have done will work, but you'll need to think about what exactly it means to evaluate expressions at compile time, and if it makes sense. I think the implementation of log will prevent you from logging at compile time, so you'd need to collect the values you're interested in inside the loop, and log them once outside the comptime block.

The other way you could fix it is to force the capture values of the loop to be evaluated at compile time by unrolling the loop using an inline 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();
    }
}

Disclaimer: There might be a better way of doing this, I'm comparatively new to the language =D

As of Zig 0.8.0 , the operand to @import is required to be a string literal .

A Zig compiler wants to know all the possibly imported files so that it can eagerly go find them and compile them when you kick off a compilation process. The design of the language is constrained by making it possible for a fast compiler to exist.

So what can we do? I think this accomplishes the task in an equivalent manner:

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();
    }
}

And the neat thing here is that, indeed, the compiler is able to eagerly fetch all 5 of those files and kick-start the compilation process, even before running the compile-time code to determine which one actually gets imported. Win-win.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM