Difference between a module and a library?

I often build small libraries to use in other Zig projects, so build methods like b.addStaticLibrary make sense. Now that we’re all using the Zig package manager, modules are a thing, so my projects also have to create a module with b.dependency in order to be found by the package manager.

My questions:

  1. Why is there so much duplication between the two? If I’m adding C files or include paths to my library, I also have to add them to the module. I never understood why. It’s almost as if the module is more than just a package manager thing.
  2. Likewise, once I’ve created my library with addStaticLibrary and add it to the build with b.installArtifact(lib), I also seem to need to add it to the module with module.linkLibrary(lib) to prevent certain errors. Again, why both?
  3. Given how much goes into a module, is addStaticLibrary redundant? Can you make a Zig-only library without building it into an official system library? Is that why there is so much overlap between libraries and modules?
  4. Is there a minimal example of wrapping a C library and making it available as a Zig-only package, without duplication? Every example I look at seems to have a lot of duplication in it. I don’t know what is actually necessary.
3 Likes

On Zig 0.14.0, library takes in your module as root_module.
So, there’s no duplication if you add stuff to module first:

const mod = b.addModule("my_lib", .{
    .target = target,
    .optimize = optimize,
    .root_source_file = root_source_file,
});
mod.addCSourceFiles(...);

// `b.addLibrary` adds a static library by default
const lib = b.addLibrary(.{
    .name = "my_lib",
    .root_module = mod,
});
2 Likes

Okay that’s great! All the more reason to migrate to 0.14.

1 Like

It’s almost as if the module is more than just a package manager thing.

Because it isn’t a package manager thing at all!

A module is a bundle of files that Zig can compile, and instructions on how to do it. Modules can contain Zig code, C code, or other files (stuff to be used with @embedFile, for example), but also specifies what headers to include if you’re building C code, what libraries to link, etc.

The package manager thing is the std.Build.Dependency returned by b.dependency, which is “just” a folder that Zig knows how to find (a link to a tarball it can unpack, a git repository it can clone, a path to a system folder, etc). The files inside the folder can be accessed with dependency.path("path/to/file").

If that folder contains a build.zig and build.zig.zon file, then the build system knows it is Zig code, and that it can contain other things that the Zig build system understands. Like modules.

When you create a library with addSharedLibrary/addStaticLibrary, you’re implicitly creating a private module, which will have all the same dependencies as the public module you’re currently exporting.

That’s where the duplication comes from.

Starting from 0.14, implicitly creating modules like that is deprecated. Instead you define the module, and then the library depends on the module, like @tensorush showed.

5 Likes

Oh wow, that is very informative and helps my understanding a lot! Thank you very much!

2 Likes