# Build Modes

> Four modes, and what each one trades.

```zig
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,
    }
}
```

*Runnable: compiled to WebAssembly and executed by CI against Zig master. (`04-build-system.build-modes`)*

| Mode | Safety checks | Optimised | Size |
| --- | --- | --- | --- |
| `Debug` | yes | no | large |
| `ReleaseSafe` | yes | yes | medium |
| `ReleaseFast` | **no** | yes | medium |
| `ReleaseSmall` | **no** | yes | small |

Select with `-O`:

```bash
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

```zig
@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.
