unreachable
is an assertion that the programmer makes to ensure program correctness and enable compiler optimizations. It is but one among a plethora of other assertions, like casting, indexing and aligning operations, all implemented as safety-checked undefined behavior.
For simplification, unreachable
’s semantics could be decomposed into several different behaviors depending on evaluation time as well as optimization mode:
-
Hitting
unreachable
at runtime emits a panic when compiled inDebug
andReleaseSafe
modes, acting exactly the same as@panic("reached unreachable code")
, but causes undefined behavior inReleaseFast
orReleaseSmall
modes. -
Hitting
unreachable
at compile-time, e.g. incomptime unreachable
, causes a compile-time error, acting exactly the same as@compileError("reached unreachable code")
.
However, the crucial semantic difference between unreachable
and @panic
is that unreachable
means that you guarantee a given assertion will never fail, while @panic
means that you accept that a given assertion could fail, in which case the program, having detected that it has entered an incorrect, unrecoverable state, will crash.
As a result, since using unreachable
at runtime anywhere in a codebase that’s intended to be compiled in unsafe modes may result in undefined behavior, you should first consider using @panic
wherever possible.
Optional unwrap
Don’t forget that a.?
is just a shorthand for:
a orelse unreachable
Debug assertion
Don’t forget that std.debug.assert
is implemented as:
pub fn assert(ok: bool) void {
if (!ok) unreachable;
}
Error discarding misuse
Should never be used to ignore errors because you would be making a guarantee that a function call will never return an error, which would contradict the function’s design of being allowed to return an error in the first place.
export fn func() void {
mayFail() catch unreachable;
}
Impossible switch
case handling
Can be used to guarantee that certain, or remaining, switch cases will never happen.
switch (my_union) {
.a => |a| { ... },
else => unreachable,
}
Static error absence guarantee
Can be used with errdefer
as a compile-time check that enforces the absence of errors in the remaining lines of the current block.
// Errors are allowed here
errdefer comptime unreachable;
// Errors are forbidden here
Explicit control flow barrier
Can be used to satisfy the compiler requirement of guaranteeing that the control flow will never reach the end of the current block.
fn withFor(any: AnySlice) usize {
const Tag = @typeInfo(AnySlice).Union.tag_type.?;
inline for (@typeInfo(Tag).Enum.fields) |field| {
if (field.value == @intFromEnum(any)) {
return @field(any, field.name).len;
}
}
// When using `inline for` the compiler doesn't know that every
// possible case has been handled requiring an explicit `unreachable`.
unreachable;
}