에러 언랩 시도
에러 언랩 시도
에러를 돌려줄 수 있는 함수에서 catch unreachable로 값을 바로 풀었는데, 그 함수가 실제로 에러를 뱉어버리면 어떻게 될까요? Zig는 조용히 넘어가지 않고 컴파일 타임이든 런타임이든 분명하게 실패시켜요. 이번 섹션에서는 그 예외 상황을 하나씩 살펴볼게요.
본문
getNumberOrFail()은 !i32 타입, 그러니까 i32를 돌려주거나 에러를 돌려줄 수 있는 함수예요. 그런데 이 함수는 항상 error.UnableToReturnNumber를 돌려줍니다. 그런 함수의 결과를 catch unreachable로 풀어버리면, 에러가 실제로 발생하는 순간 문제가 생겨요. 언제냐에 따라 실패 방식이 달라져요.
컴파일 타임에는
comptime {
const number = getNumberOrFail() catch unreachable;
_ = number;
}
fn getNumberOrFail() !i32 {
return error.UnableToReturnNumber;
}
Shell:
$ zig test test_comptime_unwrap_error.zig
/home/ci/work/zig-bootstrap/zig/doc/langref/test_comptime_unwrap_error.zig:2:44: error: caught unexpected error 'UnableToReturnNumber'
const number = getNumberOrFail() catch unreachable;
^~~~~~~~~~~
/home/ci/work/zig-bootstrap/zig/doc/langref/test_comptime_unwrap_error.zig:7:18: note: error returned here
return error.UnableToReturnNumber;
^~~~~~~~~~~~~~~~~~~
컴파일 타임 연산은 값이 다 정해져 있으니, 컴파일러가 **"caught unexpected error 'UnableToReturnNumber'"**라는 오류를 내면서 빌드를 그 자리에서 막아요. catch unreachable은 '성공이라고 보고, 에러는 절대 오지 않는다'고 약속하는 거라서, 그 약속이 깨지는 순간 컴파일 오류가 되는 거예요.
런타임에는
const std = @import("std");
pub fn main() void {
const number = getNumberOrFail() catch unreachable;
std.debug.print("value: {}\n", .{number});
}
fn getNumberOrFail() !i32 {
return error.UnableToReturnNumber;
}
Shell:
$ zig build-exe runtime_unwrap_error.zig
$ ./runtime_unwrap_error
thread 974265 panic: attempt to unwrap error: UnableToReturnNumber
error return context:
/home/ci/work/zig-bootstrap/zig/doc/langref/runtime_unwrap_error.zig:9:5: 0x11e828c in getNumberOrFail (runtime_unwrap_error.zig)
return error.UnableToReturnNumber;
^
stack trace:
/home/ci/work/zig-bootstrap/zig/doc/langref/runtime_unwrap_error.zig:4:44: 0x11e82f3 in main (runtime_unwrap_error.zig)
const number = getNumberOrFail() catch unreachable;
^
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/start.zig:788:64: 0x11e7bbb in callMain (std.zig)
if (fn_info.param_types.len == 0) return wrapMain(root.main());
^
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/start.zig:248:5: 0x11e75e1 in _start (std.zig)
asm volatile (switch (native_arch) {
^
(process terminated by signal)
런타임에는 컴파일러가 미리 알 수 없으니, 에러가 실제로 도착하는 순간 **"attempt to unwrap error: UnableToReturnNumber"**라는 메시지와 함께 패닉이 나고 프로세스가 종료돼요. 잘못된 값을 조용히 넘겨서 프로그램을 계속 돌리는 대신, 에러가 난 위치를 스택 트레이스로 남겨서 문제를 바로 찾게 해 주는 거죠.
if로 에러를 미리 확인하기
이런 충돌을 피하는 한 가지 방법은 성공을 전제하는 대신 if 표현식으로 에러인지부터 확인하는 거예요.
const print = @import("std").debug.print;
pub fn main() void {
const result = getNumberOrFail();
if (result) |number| {
print("got number: {}\n", .{number});
} else |err| {
print("got error: {s}\n", .{@errorName(err)});
}
}
fn getNumberOrFail() !i32 {
return error.UnableToReturnNumber;
}
Shell:
$ zig build-exe testing_error_with_if.zig
$ ./testing_error_with_if
got error: UnableToReturnNumber
if (result) |number|로 성공하면 값을, else |err|로 에러면 에러 객체를 받아서 각각 다르게 처리해요. 이번에는 에러가 와서 got error: UnableToReturnNumber가 출력됐지만, 어떤 경우에도 프로그램이 터지지 않고 두 경로를 안전하게 나눠 처리할 수 있어요.