Pass option with user defined type that is defined in module

I have this obviously minimized example project consisting of two files(main.zig and build.zig) here:

const std = @import("std");

pub const Kind = enum {
    none,
    one,
    two,
};
const global_kind: Kind = @import("config").kind;

pub fn main() !void {
    std.debug.print("global_kind: {}\n", .{global_kind});
}

And

const std = @import("std");
const Kind = @import("main.zig").Kind;

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const exe = b.addExecutable(.{
        .name = "passOption",
        .root_module = b.createModule(.{
            .root_source_file = b.path("main.zig"),
            .target = target,
        }),
    });

    b.installArtifact(exe);
    const run_step = b.step("run", "Run the app");
    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step);
    run_cmd.step.dependOn(b.getInstallStep());

    const options = b.addOptions();
    options.addOption(Kind, "kind", .none);
    exe.root_module.addOptions("config", options);
}

When I try to do zig build run I get this compiler error:

main.zig:8:44: error: expected type 'main.Kind', found 'options.main.Kind'
const global_kind: Kind = @import("config").kind;
                          ~~~~~~~~~~~~~~~~~^~~~~
.zig-cache/c/d4c19e2f0b6c4c41e1106ecdd72d246c/options.zig:1:26: note: enum declared here
pub const @"main.Kind" = enum (u2) {
                         ^~~~
main.zig:3:18: note: enum declared here
pub const Kind = enum {

My question is how can do something like this. I could obviously just use the Kind type defined in the config module, but that’s not really what I want to do.

I’ve already looked at some other projects, but I can’t really find an easy example, that does something like this.

I think simplest is to just convert it like so:

const global_kind: Kind = @enumFromInt(@intFromEnum(@import("config").kind));

Because options uses code generation the two enums end up as separate types, however the options generated enum gets generated with the values of the original enum so this way to convert it from one type to the other should work.

One other option would be to just declare the enum locally in your build.zig without making it public and then use the type of the config module instead.

I think beyond that your only option would be to replace your usage of addOptions with your own code generation you could then have a module that contains the Kind type and that would be imported by your generated code (basically you generate source code for the build-dependent generated value of type Kind and that code has an import for the module that contains the Kind type). This last possibility is quite a lot of effort (compared to the other two), so personally I wouldn’t do it unless there is a really good reason for it.

1 Like

Thank you. I also had this in mind but I thought there may be a better way that I don’t know about. But apparently not.

Perhaps Kind should be defined in build.zig

const std = @import("std");

const global_kind = @import("config").kind;
pub const Kind = @TypeOf(global_kind);

pub fn main() !void {
    std.debug.print("global_kind: {}\n", .{global_kind});
}
const std = @import("std");
const Kind = enum {
    none,
    one,
    two,
};

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const exe = b.addExecutable(.{
        .name = "passOption",
        .root_module = b.createModule(.{
            .root_source_file = b.path("main.zig"),
            .target = target,
        }),
    });

    b.installArtifact(exe);
    const run_step = b.step("run", "Run the app");
    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step);
    run_cmd.step.dependOn(b.getInstallStep());

    const options = b.addOptions();
    options.addOption(Kind, "kind", .none);
    exe.root_module.addOptions("config", options);
}
1 Like