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.
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
| Mode | Safety checks | Optimised |
|---|---|---|
Debug | yes | no |
ReleaseSafe | yes | yes |
ReleaseFast | no | yes |
ReleaseSmall | no | yes |
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.