One of Zig’s main design goals is cross compilation.
A Zig compiler running on one machine can produce programs for many other operating systems and processors without installing a separate toolchain.
You can build a Linux executable on macOS. You can build for ARM on x86-64. You can build freestanding binaries for systems with no operating system at all.
The target is selected with a target triple.
A target triple describes three things:
<architecture>-<operating system>-<abi>For example:
x86_64-linux-gnu
aarch64-macos-none
wasm32-freestanding-none
riscv64-linux-muslThe first field is the CPU architecture.
Common architectures include:
| Architecture | Meaning |
|---|---|
x86_64 | 64-bit x86 |
aarch64 | 64-bit ARM |
riscv64 | 64-bit RISC-V |
wasm32 | WebAssembly 32-bit |
The second field is the operating system.
| Operating system | Meaning |
|---|---|
linux | Linux |
macos | Apple macOS |
windows | Microsoft Windows |
freestanding | No operating system |
The third field is the ABI.
ABI means Application Binary Interface. It defines low-level conventions such as calling functions, object layout, and system libraries.
Common ABIs include:
| ABI | Meaning |
|---|---|
gnu | glibc-based systems |
musl | musl libc |
msvc | Microsoft Visual C++ ABI |
none | No libc or ABI runtime |
A native build uses the current machine automatically:
zig build-exe hello.zigThis produces an executable for the current operating system and processor.
To build for another target, use -target:
zig build-exe hello.zig -target x86_64-linux-gnuOn a macOS machine this produces a Linux executable.
To build for ARM Linux:
zig build-exe hello.zig -target aarch64-linux-muslTo build for WebAssembly:
zig build-exe hello.zig -target wasm32-freestanding-noneYou can inspect supported targets with:
zig targetsThe output is large. It includes architectures, operating systems, CPUs, features, and ABI combinations known to the compiler.
A small program can inspect its target at compile time.
const std = @import("std");
const builtin = @import("builtin");
pub fn main() void {
std.debug.print("arch = {s}\n", .{@tagName(builtin.cpu.arch)});
std.debug.print("os = {s}\n", .{@tagName(builtin.os.tag)});
}On Linux x86-64 the output might be:
arch = x86_64
os = linuxbuiltin contains information generated by the compiler for the current target.
builtin.cpu.arch is an enum value describing the CPU architecture.
builtin.os.tag is an enum value describing the operating system.
@tagName converts an enum tag into a string.
Since target information is available at compile time, programs can select code conditionally:
const builtin = @import("builtin");
pub fn main() void {
if (builtin.os.tag == .windows) {
windowsMain();
} else {
posixMain();
}
}
fn windowsMain() void {}
fn posixMain() void {}Only the selected branch is compiled for the target.
This is important in systems programming. Different systems often require different APIs, different calling conventions, or different executable formats.
Zig treats cross compilation as a normal operation, not a special case.
Exercise 17-1. Run zig targets and inspect the architectures supported by your compiler.
Exercise 17-2. Build the same program for two different operating systems.
Exercise 17-3. Print the target ABI with builtin.abi.
Exercise 17-4. Add target-specific code for Windows and Linux.