Reuse code from module

I am writing code for $thing.
It will have generic code (applicable to all variants) in src/thing.zig and multiple HW-specific variants in src//thing.zig

main() will call code from generic code and build system will take care to call proper function in file from HW-specific code.
So far, so good.

But, I have some data which is universal for all HW-specific code and I want to keep that data in src/thing.zig, but I also want to use that data in each of the src//thing.zig files.
That works only if none of the files is exported as module.

Example when it works (without modules):

// main.zig
const std = @import("std");
const thing = @import("thing.zig");
const GenericParameter = thing.GenericParameter;

pub fn main() !void {
	_ = thing.do_something(GenericParameter.a);
}

// hal.zig
pub const thing = @import("./variant1/thing.zig");

// src/thing.zig
const std = @import("std");
const print = std.debug.print;
const thing_specific = @import("hal.zig").thing;

pub const GenericParameter = enum {
	a,
	b,
	c,
};

pub fn do_something(arg: GenericParameter) void
{
	print("this is generic, arg: {}\n", .{arg});
	thing_specific.do_something(arg);
}


// src/variant1/thing.zig
const std = @import("std");
const print = std.debug.print;
const things_specific = @import("../thing.zig");
const GenericParameter = things_specific.GenericParameter;

pub fn do_something(arg: GenericParameter) void
{
	print("this is variant1, arg: {}\n", .{arg});
}

But if I export hal.zig as module in build.zig and change thing.zig to use that module:

const hal_mod = b.addModule("hal", .{.root_source_file = b.path("src/hal.zig")});
exe.root_module.addImport("hal", hal_mod);

// src/thing.zig
- const thing_specific = @import("hal.zig").thing;
+ const thing_specific = @import("hal").thing;

It will fail:

src/thing.zig:1:1: error: file exists in multiple modules
src/main.zig:2:23: note: imported from module root
const thing = @import(“thing.zig”);

Which seems logical because thing.zig is now in multiple compilation units (as a module defined in build.zig and as thing.zig)

What would be idiomatic thing to do in this situation?

The generic answer would be: don’t import thing.zig in main.zig, and instead get it via the hal module.

However, it seems like you might actually just want conditional compilation. Something like:

pub fn do_something(arg: GenericParameter) void {
    if (comptime some_comptime_known_condition) {
        // This branch is only evaluated when the above condition is true,
        // so you can do HW-specific things here
        print("this is variant1, arg: {}\n", .{arg});
    } else {
        // This branch is only evaluated when the above condition is false
        print("this is some other variant, arg: {}\n", .{arg});
    }
}

some_comptime_known_condition could be something like builtin.os.tag == .windows or std.Target.x86.featureSetHas(builtin.cpu.features, .aes) or whatever else you want (where builtin is const builtin = @import("builtin");)

If the info you want to branch on needs to be provided by the user, then see Options for Conditional Compilation

1 Like

You can create another module for “thing.zig”

const thing_mod = b.addModule("thing", .{.root_source_file = b.path("src/thing.zig")});
const hal_mod = b.addModule("hal", .{
    .root_source_file = b.path("src/hal.zig"),
    .imports = &.{
        .{ .name = "thing", .module = thing_module },
    },
});
exe.root_module.addImport("thing", thing_mod);
exe.root_module.addImport("hal", hal_mod);

and then use as

const thing = @import("thing");

and

const things_specific = @import("thing");
3 Likes