पाठ 16 / 25
Integer Semantics, Safety Checks and Undefined Behaviour
Understand overflow, wrapping and saturating arithmetic, and what build modes check.
Safety you can see
In Zig, integer overflow is illegal behaviour: in Debug and ReleaseSafe builds it panics with a clear message; in ReleaseFast and ReleaseSmall it is undefined behaviour that the optimiser may exploit. When you want other semantics, say so explicitly: wrapping operators +%, -%, *% wrap around (useful for hashes and counters), saturating operators +|, -|, *| clamp at the limits, and builtins such as @addWithOverflow return the result and an overflow bit. Other safety checks in safe modes: array and slice bounds, unwrapping a null optional with .?, @intCast of an out-of-range value, accessing the inactive field of a tagged union, unreachable being reached, division by zero and misaligned pointer casts. Each can be disabled locally with @setRuntimeSafety(false) in hot code once proven correct. Zig does not prevent all memory errors (use-after-free, data races, dangling pointers), unlike Rust; it offers good defaults, debugging allocators, and explicitness. A common strategy is to ship ReleaseSafe for most software and use ReleaseFast only where measurements justify it.
Build modes and safety
Debug and ReleaseSafe keep runtime checks; ReleaseFast and ReleaseSmall remove them.
Overflow, wrapping and saturating arithmetic
Explicit choices instead of silent wrap-around.
const std = @import("std");
pub fn main() void {
var hits: u8 = 250;
hits +%= 10; // wrapping: 260 mod 256 = 4
std.debug.print("wrapped: {d}\n", .{hits});
var stock: u8 = 5;
stock -|= 8; // saturating: clamps at 0 instead of underflowing
std.debug.print("saturated: {d}\n", .{stock});
const big: u32 = 4_000_000_000;
const result = @addWithOverflow(big, big); // returns .{ wrapped_result, overflow_bit }
if (result[1] == 1) {
std.debug.print("overflow detected, wrapped value {d}\n", .{result[0]});
}
const percent: u8 = 120;
const capped: u8 = @min(percent, 100);
std.debug.print("capped: {d}\n", .{capped});
// var x: u8 = 255;
// x += 1; // Debug/ReleaseSafe: panic "integer overflow"; ReleaseFast: undefined behaviour
}
fn hashBytes(data: []const u8) u32 { // FNV-1a: wrapping multiplication is intended
var h: u32 = 2166136261;
for (data) |b| {
h ^= b;
h *%= 16777619;
}
return h;
}Ship ReleaseSafe by default
The cost of bounds and overflow checks is usually small. Keeping them in production turns silent memory corruption into a clear crash with a stack trace, which is much easier to diagnose.
त्वरित जाँच: What does `x +|= 10` do for an unsigned integer near its maximum?
- Wraps around to a small number
- Panics
- Saturates at the maximum value of the type
- Converts to a larger type
Answer
Saturates at the maximum value of the type — Saturating operators clamp to the type's range.