A Zig target may use a C library, or it may use none.
These two targets are both Linux:
x86_64-linux-gnu
x86_64-linux-muslThe difference is the ABI.
gnu means the program is built for glibc.
musl means the program is built for musl libc.
A third target has no C library:
x86_64-linux-noneHere the ABI is none.
The C library matters when the program calls C code, links C libraries, uses the system allocator, starts through the normal C runtime, or depends on POSIX functions.
A simple Zig program can often avoid libc:
const std = @import("std");
pub fn main() void {
std.debug.print("hello\n", .{});
}Build it for Linux with glibc:
zig build-exe main.zig -target x86_64-linux-gnuBuild it for Linux with musl:
zig build-exe main.zig -target x86_64-linux-muslBuild it without libc:
zig build-exe main.zig -target x86_64-linux-noneThe last form is a freestanding-style build. It removes many assumptions.
A hosted program can assume an operating system and normal process services. It can open files, read environment variables, allocate through the system, and exit through the normal runtime path.
A freestanding program cannot assume those things.
const std = @import("std");
pub fn main() !void {
var file = try std.fs.cwd().createFile("out.txt", .{});
defer file.close();
try file.writeAll("hello\n");
}This program is a hosted program. It assumes a current working directory and a filesystem.
A freestanding program is closer to this:
export fn entry() void {
while (true) {}
}There may be no main. There may be no filesystem. There may be no allocator. There may be no operating system to return to.
For freestanding work, the entry point depends on the environment. A bootloader, firmware, kernel, embedded board, or WebAssembly host may call a specific exported function.
export fn add(a: i32, b: i32) i32 {
return a + b;
}This is a common shape for WebAssembly or embedded-style code: export a small function with a known ABI.
The target says what world the program is being built for. The source must match that world.
Use libc when you need C libraries or system conventions. Avoid libc when you want a smaller, more controlled binary or when no C runtime exists.
The practical rule is simple: choose the narrowest target that still gives the program what it needs.
Exercise 17-13. Build the same program with x86_64-linux-gnu and x86_64-linux-musl.
Exercise 17-14. Try to build a filesystem program for a freestanding target and read the compiler error.
Exercise 17-15. Write an exported add function and compile it for wasm32-freestanding-none.
Exercise 17-16. Explain when a program needs libc.