Whats the deal with `error: file exists in multiple modules`?

When making a library with multiple exposed modules and an executable, I can’t import a common file between the modules.

for example, given the following directory structure:

src
├── common.zig
├── main.zig
├── mod1.zig
└── mod2.zig

with mod1.zig and mod2.zig importing common.zig as a file.

and this build file:

    const mod1 = b.addModule("mod1", .{
        .root_source_file = b.path("src/mod1.zig"),
        .target = target,
        .optimize = optimize,
    });

    const mod2 = b.addModule("mod2", .{
        .root_source_file = b.path("src/mod2.zig"),
        .target = target,
        .optimize = optimize,
    });

    var exe_mod = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    exe_mod.addImport("mod1", mod1);
    exe_mod.addImport("mod2", mod2);

    const exe = b.addExecutable(.{ .name = "exe", .root_module = exe_mod });
    b.installArtifact(exe);

zig build returns: src/common.zig:1:1 error: file exists in multiple modules

The only thing that’s relevant i can find about this topic are this issue, specifically this comment:

The error is exactly what it says: files are now banned from being included in multiple packages … in general having a file in multiple packages is very bad style.

This comment is basically says my confusion with this but they got no reply explaining further so I’m still confused.

What is the reason this is bad style? If you have packages that share a common dependency (maybe some utils.zig containing some common helpers) it seems reasonable to have both of them import it as a file. I suppose the status quo is that you need to make that file a into a module and then they can both import it as a module. I don’t really see why this would be better style than directly importing the file.

If someone can shed some light on this that would be great.

If you make it into a module util so that it can be shared between the other two modules that use it, then the code in util only exists once in the internal compiler data-structures, for example if that file contains a struct definition, there will only be one of that specific struct type.

If you include the file 2 times, you will have 2 times a similar struct type that isn’t the same struct type. So you could easily end up with situations, where one module creates a struct value, tries to pass it to the other module, only to get an error that the types are different, although they look as if they are the same.

Something kind of similar to this often happens when people include cImport of the same header files multiple times, instead of putting it into a shared file and it can be quite confusing, because everything looks the same, but isn’t, because things like structs are nominal and not based on structural identity.

6 Likes