⚡ Zig Guide LiveUnofficialbut fully verified
✓ Zig 0.17.0-dev.2122+3e15e99e6What's newOn an older Zig?

Emitting an Executable

For a single file you do not need a build script at all:

zig build-exe main.zig          # an executable
zig build-lib  main.zig         # a static library
zig build-obj  main.zig         # an object file
zig run        main.zig         # build and run in one step
zig test       main.zig         # build and run its tests

Useful flags

FlagEffect
-O<Mode>optimisation mode
-target <triple>cross-compile
--name <n>output name
-femit-bin=<path>where to write the binary
-fstripdrop debug info
-fsingle-threadedassume no threads

zig run is the one you will use most

The difference between run and build-exe is only where the binary ends up:

zig run foo.zig

      ├── compile
      ├── binary into the cache
      └── execute it

zig build-exe foo.zig

      └── compile


           ./foo

build-exe leaves an artifact in your working directory for you to run, ship, or link. run puts the same artifact in the cache and executes it immediately, so there is nothing to clean up and nothing to add to .gitignore.

For anything up to a few hundred lines, zig run main.zig is the whole workflow. It compiles to a cache directory and executes, so there is no binary left in your working tree and no build step to remember. Arguments for the program go after --:

zig run main.zig -- --verbose input.txt

The cache is content-addressed, so running it again after no changes does not recompile. That makes zig run fast enough to use like a script interpreter, which is a reasonable way to work on a single-file tool for a surprisingly long time.

zig test works the same way, and needs no main. A file that is nothing but test blocks is a complete, runnable program as far as Zig is concerned.

What each command actually produces

build-exe produces something the OS can run, which means it needs an entry point and it links. build-lib produces a .a (or .lib) for someone else to link, and adding -dynamic gives you a shared library instead. build-obj stops before linking and hands you a single object file, which is what you want when Zig is one part of a build driven by something else.

That last one is the quiet route into an existing codebase. Compile one module with build-obj and link it into a C or C++ project exactly like any other object file. Nothing else about the project has to change.

Targeting WebAssembly

This is how every snippet on this site is built:

zig build-exe main.zig -target wasm32-wasi -OReleaseSmall

For a freestanding wasm module with no WASI (the browser-canvas case), you want -target wasm32-freestanding plus -fno-entry and explicit exports:

zig build-exe main.zig -target wasm32-freestanding \
  -OReleaseSmall -fno-entry --export=add

The difference between the two is what the module expects the host to provide. wasm32-wasi assumes a filesystem-shaped interface and a main; wasm32-freestanding assumes nothing, so there is no entry point to call and you name the functions the host may call instead. -OReleaseSmall matters more here than anywhere else, because the artifact is downloaded before it runs.

When to graduate to build.zig

Once you have more than one artifact, dependencies, generated files, or a test step worth naming, move to a build script. zig build --help then lists the steps and options that script defines.

The threshold in practice is usually the second target or the first dependency. As long as one command line builds the thing, the command line is simpler than a script. As soon as you are keeping a note of which flags to pass, that note should be build.zig.