Build Modes
const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;
test "the current mode is known at compile time" {
// These snippets are built as .small. Master lowercased the mode tags:
// .Debug/.ReleaseSafe/.ReleaseFast/.ReleaseSmall are now
// .debug/.safe/.fast/.small.
try expect(builtin.mode == .small);
}
test "safety checks follow the mode" {
// True in .debug and .safe, false in .fast and .small.
const safety_on = switch (builtin.mode) {
.debug, .safe => true,
.fast, .small => false,
};
try expect(!safety_on); // because we are in .small
}
test "safety can be forced back on for a scope" {
// Useful for keeping bounds checks in one risky function of an
// otherwise ReleaseFast build.
@setRuntimeSafety(true);
var index: usize = 1;
_ = &index;
const array = [_]u8{ 1, 2, 3 };
try expect(array[index] == 2);
}
test "branch hints and unreachable" {
// In safety builds `unreachable` panics; in ReleaseFast it is a promise
// to the optimiser, and reaching it is illegal behaviour.
const value: u8 = 2;
switch (value) {
1, 2, 3 => {},
else => unreachable,
}
}| Mode | Safety checks | Optimised | Size |
|---|---|---|---|
Debug | yes | no | large |
ReleaseSafe | yes | yes | medium |
ReleaseFast | no | yes | medium |
ReleaseSmall | no | yes | small |
Select with -O:
zig build-exe main.zig -OReleaseSafe
zig build -Doptimize=ReleaseSafe
ReleaseSafe deserves more use than it gets
The reflex from C and C++ is that release means unchecked. Zig makes that a
separate axis: ReleaseSafe is optimised and keeps bounds checks, overflow
checks, and null-unwrap checks. For most software the cost is small and the
alternative is silent memory corruption.
Reach for ReleaseFast when you have measured that the checks matter, not by
default.
Where the checks are removed, the rules do not change
In ReleaseFast and ReleaseSmall, an out-of-bounds index is illegal
behaviour, the same category as C’s undefined behaviour. The check was a
diagnostic for breaking the rule, not the definition of it. Code that only
works because Debug caught the panic is already wrong.
Per-scope override
@setRuntimeSafety(true);
Keeps checks in one function of an otherwise unchecked build, useful for the one routine parsing untrusted input.
Knowing the mode at compile time
@import("builtin").mode is a comptime value, so mode-specific code costs
nothing at runtime: the dead branch is not compiled in.