Linking is the step where the compiler connects your program with the code it depends on.
Linking is the step where the compiler connects your program with the code it depends on.
Your program may use code from:
your own Zig files
the Zig standard library
C libraries
system libraries
third-party packagesAfter compilation, those pieces need to become one runnable program. The linker does that job.
There are two main styles:
static linking
dynamic linkingStatic linking copies library code into the final executable.
Dynamic linking keeps library code outside the executable and loads it when the program starts.
Static Linking
With static linking, the final executable contains the library code it needs.
That means the executable can often run without installing extra library files on the target machine.
For example, if your program uses a static library named:
libmath.athe linker copies the needed code from that library into your program.
The result is one larger executable.
Static linking is useful when you want simple deployment. You can copy one file to a server and run it.
Dynamic Linking
With dynamic linking, the final executable does not contain all library code.
Instead, it records that it needs a shared library.
Examples of shared libraries:
Linux: libssl.so
macOS: libssl.dylib
Windows: ssl.dllWhen your program starts, the operating system loads those libraries.
The result is usually a smaller executable, but the target machine must have the right shared libraries available.
Static vs Dynamic
| Linking style | What happens | Main benefit | Main cost |
|---|---|---|---|
| Static | Library code is copied into the executable | Easier deployment | Larger binary |
| Dynamic | Library code is loaded at runtime | Smaller binary, shared system libraries | Runtime dependency problems |
For beginner Zig projects, static linking is often simpler. For desktop apps, system integration, plugins, and large shared libraries, dynamic linking may be better.
A Simple Zig Executable
A normal executable starts like this:
const exe = b.addExecutable(.{
.name = "hello",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});If this program only uses Zig code, you usually do not need to think much about linking.
Linking becomes more visible when you use C libraries, system libraries, or when you choose static or dynamic outputs yourself.
Linking a C Source File
Zig can compile C code as part of the build.
Suppose your project looks like this:
app/
build.zig
src/
main.zig
add.c
add.hThe C file:
int add(int a, int b) {
return a + b;
}The header:
int add(int a, int b);In build.zig, you can add the C file:
exe.addCSourceFile(.{
.file = b.path("src/add.c"),
.flags = &.{},
});Then link the C runtime if needed:
exe.linkLibC();Now Zig builds the Zig code and the C code into the final executable.
Linking a System Library
Sometimes you need a library that already exists on the system.
For example, on Unix-like systems, you might link the math library:
exe.linkSystemLibrary("m");This tells the linker to link against libm.
A more common real-world example is linking a library such as SQLite, OpenSSL, or zlib:
exe.linkSystemLibrary("sqlite3");This assumes the target system or build environment has that library available.
If the library is missing, the link step fails.
Linking libc
C libraries often require libc.
In Zig, you can request libc with:
exe.linkLibC();This matters when:
you call C functions
you compile C source files
you link C libraries that depend on libcPure Zig programs may not need libc. That can make them easier to cross compile and deploy.
Static Libraries
A static library usually has one of these forms:
Linux/macOS: libname.a
Windows: name.libYou can link a static library by adding its path and library name.
A simple pattern is:
exe.addLibraryPath(b.path("vendor/mylib/lib"));
exe.linkSystemLibrary("mylib");If the linker finds a static version, it can link that.
For more direct control, projects often use Zig packages or build the static library as part of the same build.
Dynamic Libraries
A dynamic library usually has one of these forms:
Linux: .so
macOS: .dylib
Windows: .dllDynamic linking means the executable expects that library to be available when it runs.
This can fail at runtime if the library cannot be found.
For example:
error while loading shared libraries: libexample.so: cannot open shared object fileThis does not mean your Zig code failed to compile. It means the operating system could not find a required dynamic library when starting the program.
Building a Static Library in Zig
Zig can build libraries too.
A static library build target looks like this:
const lib = b.addStaticLibrary(.{
.name = "mylib",
.root_module = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(lib);This produces a static library artifact.
Static libraries are useful when you want other programs to link your code into their own executable.
Building a Dynamic Library in Zig
A dynamic library build target looks like this:
const lib = b.addSharedLibrary(.{
.name = "mylib",
.root_module = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(lib);This produces a shared library.
A shared library is useful when code should be loaded by another program at runtime, or when multiple programs should share one installed copy of a library.
Exporting Functions from a Library
If you want C or another language to call a Zig function, export it:
export fn add(a: c_int, b: c_int) c_int {
return a + b;
}The word export makes the function visible outside the compiled library or executable.
The type c_int is used for C ABI compatibility.
A dynamic library containing this function can be called from C, Python FFI, Rust FFI, or another host that can load C-compatible symbols.
Static Linking and Cross Compilation
Static linking is often useful with cross compilation.
For example:
zig build -Dtarget=x86_64-linux -Doptimize=ReleaseFastIf the result is mostly self-contained, deployment is easier.
But static linking becomes harder when the program depends on large C libraries or platform-specific system libraries.
The rule is simple: every linked library must match the target.
A macOS library cannot be linked into a Linux executable. A Linux x86_64 library cannot be linked into a Windows executable. An ARM library cannot be linked into an x86_64 executable.
Dynamic Linking and Deployment
Dynamic linking makes deployment more sensitive.
You need to answer:
Which shared libraries does the program need?
Where will the operating system look for them?
Are the library versions compatible?
Are they installed on the target machine?For server tools, this can be annoying.
For desktop applications, this may be normal.
For plugins, dynamic linking may be required.
Checking Linked Libraries
On Linux, you can inspect dynamic library dependencies with:
ldd ./helloOn macOS:
otool -L ./helloOn Windows, tools such as Dependency Walker or dumpbin can inspect DLL dependencies.
These tools help you see what your executable expects at runtime.
Build Mode and Linking
Optimization mode and linking are separate ideas.
This controls optimization:
zig build -Doptimize=ReleaseFastThis controls target:
zig build -Dtarget=x86_64-linuxLinking choices are usually described inside build.zig:
exe.linkLibC();
exe.linkSystemLibrary("sqlite3");Do not confuse release mode with static linking. A release build can still use dynamic libraries. A debug build can still link statically.
Common Mistakes
A common mistake is assuming a program is fully self-contained because it compiled successfully.
Compilation and runtime loading are different. A dynamically linked program may compile correctly but fail to start on another machine.
Another common mistake is linking against a library for the host instead of the target during cross compilation.
Another mistake is forgetting linkLibC() when using C code or C libraries that require libc.
Another mistake is exporting Zig functions without C-compatible types when the function is meant to be called from C.
The Important Idea
Linking decides how your program connects with library code.
Static linking puts library code into the executable.
Dynamic linking loads library code at runtime.
In build.zig, the common linking calls are:
exe.linkLibC();
exe.linkSystemLibrary("name");
exe.addCSourceFile(.{
.file = b.path("src/file.c"),
.flags = &.{},
});For pure Zig programs, linking is often simple. For C libraries, system libraries, cross compilation, and shared libraries, linking becomes one of the most important parts of the build.