Zig inserts safety checks for operations that are valid only under certain conditions. These checks are present in safe build modes. They catch mistakes at the point where the...
Zig inserts safety checks for operations that are valid only under certain conditions. These checks are present in safe build modes. They catch mistakes at the point where the mistake happens.
Array indexing is checked.
const std = @import("std");
pub fn main() void {
const a = [_]u8{ 1, 2, 3 };
std.debug.print("{d}\n", .{a[3]});
}The array has indexes 0, 1, and 2. Index 3 is outside the array.
In Debug mode, this is a panic.
panic: index out of boundsArithmetic overflow is checked.
const std = @import("std");
pub fn main() void {
var x: u8 = 255;
x += 1;
std.debug.print("{d}\n", .{x});
}The value 256 cannot fit in u8.
In a safe build mode, this is a panic.
panic: integer overflowUse wrapping arithmetic when wrapping is the intended operation.
const std = @import("std");
pub fn main() void {
var x: u8 = 255;
x +%= 1;
std.debug.print("{d}\n", .{x});
}The result is:
0Unwrapping an optional is checked.
const std = @import("std");
pub fn main() void {
const x: ?u32 = null;
std.debug.print("{d}\n", .{x.?});
}The expression:
x.?means: take the value inside the optional.
If the optional is null, there is no value to take. In a safe build mode, this is a panic.
Use if to handle the null case.
const std = @import("std");
pub fn main() void {
const x: ?u32 = null;
if (x) |value| {
std.debug.print("{d}\n", .{value});
} else {
std.debug.print("no value\n", .{});
}
}Error unions are also checked when forced.
const std = @import("std");
fn fail() !u32 {
return error.Failed;
}
pub fn main() void {
const value = fail() catch unreachable;
std.debug.print("{d}\n", .{value});
}The word unreachable tells the compiler that control cannot reach this point.
If it is reached in a safe mode, Zig panics.
panic: reached unreachable codeUse try, catch, or an explicit branch to handle the error.
const std = @import("std");
fn fail() !u32 {
return error.Failed;
}
pub fn main() void {
const value = fail() catch |err| {
std.debug.print("error: {}\n", .{err});
return;
};
std.debug.print("{d}\n", .{value});
}Alignment is checked when asserted.
const std = @import("std");
pub fn main() void {
var bytes = [_]u8{ 1, 2, 3, 4, 5 };
const p: *u32 = @ptrCast(@alignCast(&bytes[1]));
std.debug.print("{d}\n", .{p.*});
}The address of bytes[1] may not be aligned for u32.
@alignCast asserts that the pointer has the required alignment. If the assertion is false in a safe build mode, the program panics.
Safety checks are not a substitute for correct code. They are a way to make wrong code fail close to the source of the error.
The programmer still chooses the operation.
Checked addition:
x += 1;Wrapping addition:
x +%= 1;Checked optional unwrap:
const y = x.?;Handled optional:
if (x) |y| {
// use y
}Unchecked assumption:
unreachableHandled error:
const y = f() catch |err| {
// handle err
};A useful habit is to write the program first in Debug mode. Let the checks find bad indexes, bad unwraps, overflow, wrong alignment, and false assumptions.
Then decide which checks must remain in the released program.
The build mode controls this.
| Mode | Safety checks | Main use |
|---|---|---|
| Debug | On | Development |
| ReleaseSafe | On | Safer optimized builds |
| ReleaseFast | Mostly off | Speed |
| ReleaseSmall | Mostly off | Size |
The exact cost of a check depends on the operation and the target. Some checks compile away when the compiler can prove they are unnecessary.
For example:
const std = @import("std");
pub fn main() void {
const a = [_]u8{ 10, 20, 30 };
for (a) |x| {
std.debug.print("{d}\n", .{x});
}
}The loop ranges over the array itself. There is no separate index that can go out of bounds.
This form gives the compiler more information and gives the programmer fewer ways to make an indexing mistake.
Safety in Zig is mostly explicit. The language checks the sharp edges, but it does not remove them.
Exercise 19-5. Write a program that unwraps a null optional with .?, then rewrite it with if.
Exercise 19-6. Write a program that reaches unreachable, then rewrite it to handle the case normally.
Exercise 19-7. Compare + and +% on u8 values near 255.
Exercise 19-8. Rewrite an index-based loop as a direct for loop over the array.