unreachable — 절대 도달해서는 안 되는 코드를 컴파일러에게 알려줘요
unreachable — 절대 도달해서는 안 되는 코드를 컴파일러에게 알려줘요
코드를 짜다 보면 "이 자리에 흐름이 오는 건 절대 없어"라고 확신하는 순간이 있죠. 예를 들어 어떤 조건이 거짓일 리 없다거나, 한 분기 끝까지 자연스럽게 실행이 이어질 일이 없다거나요. 그럴 때 Zig에는 이 사실을 명시적으로 선언하는 키워드가 있어요. 바로 unreachable입니다. unreachable은 "여기 도달하면 안 되는데 도달했다"는 사실 그 자체를 뜻해요.
본문
unreachable의 동작은 빌드 모드에 따라 크게 두 갈래로 나뉘어요.
먼저 Debug와 ReleaseSafe 모드에서는, unreachable 코드에 도달하면 reached unreachable code 라는 메시지와 함께 panic을 호출해요. 즉 심각한 버그로 간주하고 프로그램을 즉시 중단시키는 거죠.
반면 ReleaseFast와 ReleaseSmall 모드에서는, 옵티마이저가 "이 코드는 결코 실행되지 않는다"는 가정을 바탕으로 최적화를 수행해요. 실행 시점의 검사 대신 컴파일 시점의 확신으로 성능을 얻는 방식이에요.
Basics
unreachable은 제어 흐름이 특정 위치에 절대 도달하지 않는다는 것을 단언(assert)하는 데 쓰여요:
// unreachable is used to assert that control flow will never reach a
// particular location:
test "basic math" {
const x = 1;
const y = 2;
if (x + y != 3) {
unreachable;
}
}
$ zig test test_unreachable.zig
1/1 test_unreachable.test.basic math...OK
All 1 tests passed.
실제로 std.debug.assert도 바로 이렇게 구현되어 있어요:
// This is how std.debug.assert is implemented
fn assert(ok: bool) void {
if (!ok) unreachable; // assertion failure
}
// This test will fail because we hit unreachable.
test "this will fail" {
assert(false);
}
$ zig test test_assertion_failure.zig
1/1 test_assertion_failure.test.this will fail...thread 971976 panic: reached unreachable code
/home/ci/work/zig-bootstrap/zig/doc/langref/test_assertion_failure.zig:3:14: 0x1253949 in assert (test_assertion_failure.zig)
if (!ok) unreachable; // assertion failure
^
/home/ci/work/zig-bootstrap/zig/doc/langref/test_assertion_failure.zig:8:11: 0x125391e in test.this will fail (test_assertion_failure.zig)
assert(false);
^
/home/ci/work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:295:25: 0x1207381 in mainTerminal (test_runner.zig)
if (test_fn.func()) |_| {
^
/home/ci/work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:74:28: 0x1206b52 in main (test_runner.zig)
return mainTerminal(init);
^
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/start.zig:789:88: 0x12032af in callMain (std.zig)
if (fn_info.param_types[0].? == std.process.Init.Minimal) return wrapMain(root.main(.{
^
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/start.zig:248:5: 0x1202c81 in _start (std.zig)
asm volatile (switch (native_arch) {
^
error: the following test command terminated with signal ABRT:
/home/ci/work/zig-bootstrap/out/zig-local-cache/o/81bf7eda0306df7bc42a1bc63df72f0c/test --seed=0x5361da06
위 코드를 실행하면 assert(false) 때문에 unreachable에 도달해서 panic이 나고, "reached unreachable code" 메시지와 함께 스택 트레이스가 출력되며 테스트가 실패해요. assert가 실패하는 순간이 곧 "도달해서는 안 되는 코드를 도달한" 순간이기 때문이에요.
At Compile-Time
unreachable은 컴파일 타임에도 의미가 있어요. 다음을 봐요:
const assert = @import("std").debug.assert;
test "type of unreachable" {
comptime {
// The type of unreachable is noreturn.
// However this assertion will still fail to compile because
// unreachable expressions are compile errors.
assert(@TypeOf(unreachable) == noreturn);
}
}
$ zig test test_comptime_unreachable.zig
/home/ci/work/zig-bootstrap/zig/doc/langref/test_comptime_unreachable.zig:10:16: error: unreachable code
assert(@TypeOf(unreachable) == noreturn);
^~~~~~~~~~~~~~~~~~~~
/home/ci/work/zig-bootstrap/zig/doc/langref/test_comptime_unreachable.zig:10:24: note: control flow is diverted here
assert(@TypeOf(unreachable) == noreturn);
^~~~~~~~~~~
여기서 두 가지 흥미로운 점을 볼 수 있어요. 첫째, unreachable의 타입은 noreturn 이에요. 둘째, 위 주석에서도 친절히 알려주듯, unreachable 표현식은 런타임 검사처럼 동작하는 게 아니라 그 자체가 컴파일 에러예요. 그래서 comptime 블록 안에서 @TypeOf(unreachable) == noreturn을 확인하려 하면, 검사에 도달하기 전에 이미 "unreachable code" 라는 컴파일 오류가 나요. 왜냐하면 unreachable에 도달하는 그 순간, 제어 흐름이 거기서 끊겨 버리기(divert) 때문이에요.
같이 보면 좋은 내용:
더 알아보기
unreachable과 짝을 이루는 타입이 바로noreturn이에요.unreachable의 타입이noreturn이고,noreturn타입은 다음 섹션에서 이어서 다룹니다.- Debug/ReleaseSafe처럼 안전한 모드에서는
unreachable이 panic으로 이어져 버그를 즉시 드러내요. 반대로 최적화 모드에서는 "도달하지 않는다는 가정"으로 더 빠른 코드로 바꿔줘요. 그래서unreachable은 성능과 안전성 모두에서 컴파일러와 소통하는 강력한 도구랍니다. - 이 키워드는
std.debug.assert처럼 "절대 실패하면 안 되는 검사"를 직접 구현할 때 특히 유용해요. 검사가 깨졌을 때 최대한 빨리, 뚜렷하게 알려주게 하려는 거예요.