Skip to Content
1. Getting Started1.3 The Zig Build System

The Zig Build System

For simple projects, compiling and executing can be combined using the zig run command:

$ zig run ./main.zig

This command compiles main.zig and immediately runs the resulting executable. While convenient, this approach doesn’t scale well for projects with multiple files, dependencies, or complex build requirements.

Creating a Project with Zig

We created the “Hello, world!” project manually by first making a directory then creating a main.zig file to store our code. Zig offers a better way to create projects through zig init. We first need to create a directory for the project, then run this command to have Zig set everything up for us. This is how we could have created the project in the previous section:

$ cd ~/projects $ mkdir hello_world $ cd hello_world $ zig init

The zig init command creates the following project structure:

hello_world ├─ src │ ├─ main.zig │ ├─ root.zig ├─ build.zig ├─ build.zig.zon

If you are writing an executable program such as our “Hello, world!” project, you will be writing code in main.zig. The convention is to delete the root.zig file. If you are writing a library, however, root.zig will be the root source file of the library. This means you can delete the main.zig file.

Both files are created with some boilerplate code. If you’re curious, take a look at the code in the files. You can safely delete all of the code in either file and use the one necessary for your project (executable or library).

You may have noticed two files we have not yet discussed: build.zig and build.zig.zon. The following sections will explain their roles. The files contain helpful comments that explain the purpose of the code, so consider reading them if you’re interested.

For now, do not delete the code in build.zig or build.zig.zon; otherwise, your projects will fail to build. If you are more experienced, feel free to modify the files.

  • If you delete code in build.zig, the Zig compiler won’t know what to compile or how to compile it.
  • If you delete code in build.zig.zon, you will encounter errors because required fields in the file are missing.

Note: zig run With the project structure described above, we can no longer run our code with zig run ./main.zig because the main.zig is not in the project root. We have to use the correct path to the file: zig run src/main.zig.

Package Management

For managing real-world projects, Zig provides a powerful integrated build system invoked via the zig build command. This eliminates the need for external build tools like CMake.

At the heart of the build system is the build.zig file, which should be located in your project’s root directory. This is a regular Zig source file containing instructions written in Zig code that define how your project should be built (e.g., compiling source files, linking libraries, creating executables, running tests).

When you execute zig build, the Zig compiler looks for build.zig in the current directory. It then compiles and runs this build.zig script. The script, using the build system APIs provided by the standard library, orchestrates the compilation and linking of your entire project according to the logic you’ve defined.

Note: Library Functions When writing library functions, it is necessary to add export before function declarations so that they are available in the public library object created by the Zig compiler.

Project Configuration and Dependencies

Alongside build.zig, you will often have a build.zig.zon file. The .zon extension stands for Zig Object Notation, a data format similar to JSON but designed for Zig. It primarily serves to:

  • Declare metadata about your project (name, versions, etc.).
  • Delcare dependencies you project needs from external sources.

The build.zig.zon file is similar to package.json in Node.js and Cargo.toml in Rust.

Zig has a package manager that is integrated with the build system. This package manager reads the dependency information from build.zig.zon and handles tasks like fetching and making them available to your build.zig script. Your build.zig script then uses functions like b.dependency() to incorporate these declared dependencies into the build process.

Zig’s package manager is under active development and not as mature as Cargo in Rust. This means that it can change often and documentation can be out-of-date.

Build Output

By default, the zig build command places compiled artifacts (executables, libraries) into a zig-out directory created in your project root. You can then run your executables directly, typically from:

$ ./zig-out/bin/your_project_name # The project name will be the name you defined in build.zig

Build Modes

Zig allows you to use different build-optimization modes called Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. See the table for a quick reference or read below for more information. UB refers to undefined behavior.

ModeGoalOptimizationsSafetyDebug Info
DebugEasiest debuggingDisabled for faster compilationMaximumFull
ReleaseSafeOptimized with safety checksEnabledUB will panicMinimal
ReleaseFastMaximum runtime performanceEnabled and tuned for speedSilent errorsNone
ReleaseSmallSmallest binary sizeEnabled and tuned for sizeSilent errorsNone
  1. Debug (default):
  • Goal: Easiest debugging experience.
  • Optimizations: Optimizations are disabled to make compile times faster. It ensures the compiled code maps closely to the source code for debugging.
  • Safety: Enables the maximum amount of safety checks for undefind behavior, but results in worse runtime performance due to these checks.
  • Debug Info: Full debug information.
  1. ReleaseSafe:
  • Goal: Create an optimized release build with safety checks.
  • Optimizations: Optimizations are enabled.
  • Safety: Safety checks are enabled and undefined behavior will cause the program to panic (crash). This prevents silent errors but incurs a small runtime performance cost compared to ReleaseFast.
  • Debug Info: Minimal to none.
  1. ReleaseFast:
  • Goal: Maximum runtime performance.
  • Optimizations: Enabled and tuned for speed.
  • Safety: Safety checks are disabled. Undefined behavior might occur without warning. This has the best performance but requires careful coding.
  • Debug Info: Usually none.
  1. ReleaseSmall:
  • Goal: Minimum binary size.
  • Optimizations: Enabled and tuned for size.
  • Safety: Safety checks are disabled, similar to ReleaseFast. Undefined behavior might occur without warning.
  • Debug Info: Usually none. Useful for embedded systems or environments where binary size is critical.

These build modes can be set with the flag: -Doptimize=<mode>:

$ zig build -Doptimize=ReleaseFast

It is best to be explicit about which mode you prefer when building your code. You can configure a default mode in zig.build. If you omit the optimization flag, Zig defaults to Debug.

Last updated on