Safety, Undefined Behavior and Build Modes

29.1 Build (Optimize) Modes

Runtime safety checks Debug (default) Development (fast compile, best debugging)

Production where safety matters (servers) Max performance (games, compute) Embedded, WASM, small binaries

zig build-exe main.zig -O ReleaseSafe
zig build -Doptimize=ReleaseFast

29.2 Illegal Behavior Caught in Safe Modes

Integer overflow and divide by zero, out-of-bounds indexing/slicing, null unwrapping (.?), unreachable reached, invalid enum/union tag access, @intCast out of range, incorrect alignment (@alignCast), reading the inactive union field, shift amount too large, integer part of a float out of range, @divExact inexact, slice sentinel mismatch.

In ReleaseFast/Small these are undefined behavior, which the optimizer may exploit.

29.3 Controlling Safety Locally

fn hotLoop(data: []u32) u32 {
    @setRuntimeSafety(false);       // disable checks in this scope only
    var sum: u32 = 0;
    for (data) |x| sum +%= x;
    return sum;
}
fn mustCheck() void {
    @setRuntimeSafety(true);        // force checks even in ReleaseFast
}

29.4 Detection Beyond Builtin Checks

DebugAllocator detects leaks, double frees, and use-after-free (with safety/never_unmap options).

undefined is filled with 0xAA in Debug. Valgrind integration (std.valgrind). Sanitizers (-fsanitize-c for C code, ThreadSanitizer -fsanitize-thread).

Not prevented by the language: use-after-free in general, data races, dangling stack pointers, aliasing bugs. Zig relies on tooling and discipline here, unlike Rust's compile-time borrow checking.