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=ReleaseFast29.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.