⚡ Zig Guide LiveUnofficial
✓ Zig 0.17.0-dev.1454+5faa79730On an older Zig?

Runtime Safety

Zig inserts runtime checks for a class of mistakes that C leaves undefined: out-of-bounds indexing, integer overflow, invalid casts, unwrapping a null optional, and more. When a check fails the program panics with a clear message instead of quietly reading whatever came next in memory.

Deliberately panics. Compiled by CI to prove it is still valid, but not executed.
const std = @import("std");

pub fn main(init: std.process.Init) !void {
    _ = init;

    // Zig inserts checks for out-of-bounds access, integer overflow, invalid
    // casts, and more. In a safety-enabled build this panics with
    // "index out of bounds" rather than reading whatever memory follows.
    const array = [_]u8{ 1, 2, 3 };
    var index: usize = 5;
    _ = &index; // defeat comptime evaluation
    const value = array[index];
    std.debug.print("never printed: {d}\n", .{value});
}

// This snippet is marked `//! norun` because it deliberately panics: CI
// compiles it to prove the code is still valid, but does not execute it.

Safety depends on the build mode

ModeSafety checksOptimised
Debugyesno
ReleaseSafeyesyes
ReleaseFastnoyes
ReleaseSmallnoyes

This is the decision C never gives you explicitly. ReleaseSafe keeps the checks and is a genuinely reasonable default for production; ReleaseFast trades them for speed.

Where a check is removed, the situation it was catching becomes illegal behaviour: the same category as C’s undefined behaviour, with the same consequences. The checks are not the definition of correctness, they are a diagnostic for violating it.

You can override the setting for a scope with @setRuntimeSafety(true), which is useful for keeping bounds checks in one hot-but-hairy function of an otherwise ReleaseFast build.