A library is code meant to be used by another program. In Zig, a library can be built from the same kind of source files as an executable. The difference is in how the build...
A library is code meant to be used by another program. In Zig, a library can be built from the same kind of source files as an executable. The difference is in how the build script exposes it.
We will make a small library named mathx.
It has one source file:
src/mathx.zigpub fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn mul(a: i32, b: i32) i32 {
return a * b;
}The functions are marked pub so code outside the file can use them.
Now create build.zig.
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lib = b.addStaticLibrary(.{
.name = "mathx",
.root_source_file = b.path("src/mathx.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
}Build it:
zig buildThe result is a static library in Zig’s build output directory.
A static library is linked into the final program at build time. The executable receives the compiled code it uses. There is no separate dynamic library to load at run time.
A library is more useful with tests. Put the tests beside the code.
const std = @import("std");
pub fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn mul(a: i32, b: i32) i32 {
return a * b;
}
test "add" {
try std.testing.expectEqual(@as(i32, 30), add(10, 20));
}
test "mul" {
try std.testing.expectEqual(@as(i32, 42), mul(6, 7));
}The build script needs a test step.
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lib = b.addStaticLibrary(.{
.name = "mathx",
.root_source_file = b.path("src/mathx.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
const tests = b.addTest(.{
.root_source_file = b.path("src/mathx.zig"),
.target = target,
.optimize = optimize,
});
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&run_tests.step);
}Run the tests:
zig build testThe test code is compiled only for the test artifact. It does not become part of the installed library.
A library can also expose a module for other Zig code. Add this to the build script:
const mathx_mod = b.addModule("mathx", .{
.root_source_file = b.path("src/mathx.zig"),
});A program in the same build can import that module.
const std = @import("std");
const mathx = @import("mathx");
pub fn main() void {
const n = mathx.add(10, 20);
std.debug.print("{d}\n", .{n});
}The build script must connect the module to the executable.
const exe = b.addExecutable(.{
.name = "demo",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("mathx", mathx_mod);
b.installArtifact(exe);Now the full build script has both a library and a program that uses it.
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const mathx_mod = b.addModule("mathx", .{
.root_source_file = b.path("src/mathx.zig"),
});
const lib = b.addStaticLibrary(.{
.name = "mathx",
.root_source_file = b.path("src/mathx.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
const exe = b.addExecutable(.{
.name = "demo",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("mathx", mathx_mod);
b.installArtifact(exe);
const tests = b.addTest(.{
.root_source_file = b.path("src/mathx.zig"),
.target = target,
.optimize = optimize,
});
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&run_tests.step);
}This build has three separate outputs:
| Output | Purpose |
|---|---|
| static library | compiled library artifact |
| executable | program that imports the module |
| test artifact | test runner for the library |
The source file is the same. The build graph decides how it is used.
This is an important distinction in Zig. Source files do not declare themselves to be packages, libraries, or executables. The build script says what to build.
Exercise 20-21. Add a sub function and test it.
Exercise 20-22. Add a div function that returns an error on division by zero.
Exercise 20-23. Build the library in release mode.
Exercise 20-24. Add a second executable that uses the same module.
Exercise 20-25. Rename the module without renaming the library artifact.