Invalid Enum Cast — 존재하지 않는 태그 값으로 enum을 만들면 어떻게 될까요?
Invalid Enum Cast — 존재하지 않는 태그 값으로 enum을 만들면 어떻게 될까요?
enum은 자기 태그 값에 없는 정수로는 만들 수 없어요. 그런데 만약에 그런 값을 억지로 넣으려 한다면요? Zig는 그 상황이 컴파일 타임에 드러나는지, 런타임에만 벌어지는지에 따라 다르게 반응합니다. 두 경우를 나란히 보면서 어떻게 막아주는지 확인해볼게요.
본문
컴파일 타임 — 값을 알 수 있어서 아예 걸러집니다
값이 컴파일 타임에 정해져 있어서 어떤 결과가 나올지 미리 알 수 있다면, Zig는 컴파일 에러로 잡아줘요. 아래 코드는 u2 값 3을 Foo로 바꾸려고 하는데, Foo에는 태그 값이 a=0, b=1, c=2뿐이라 3에 해당하는 태그가 없죠.
const Foo = enum(u2) {
a,
b,
c,
};
comptime {
const a: u2 = 3;
const b: Foo = @fromBackingInt(a);
_ = b;
}
comptime 블록 안이라서 a의 값 3이 컴파일 타임에 확정되어 있어요. 그래서 실행되기도 전에 컴파일러가 문제를 찾아내요.
$ zig test test_comptime_invalid_enum_cast.zig
/home/ci/work/zig-bootstrap/zig/doc/langref/test_comptime_invalid_enum_cast.zig:8:20: error: enum 'test_comptime_invalid_enum_cast.Foo' has no tag with value '3'
const b: Foo = @fromBackingInt(a);
^~~~~~~~~~~~~~~~~~
/home/ci/work/zig-bootstrap/zig/doc/langref/test_comptime_invalid_enum_cast.zig:1:13: note: enum declared here
const Foo = enum(u2) {
^~~~
핵심 메시지는 enum '...Foo' has no tag with value '3'예요. 말 그대로 값 3에 해당하는 태그가 enum에 없다는 뜻이죠.
런타임 — 값을 미리 알 수 없으면 패닉으로 막습니다
값이 런타임에 들어와서 컴파일 타임에는 알 수 없는 경우, 에러 대신 실행 중에 패닉(panic)이 발생해요. 아래 코드는 함수 인자로 받은 a를 Foo로 바꾸는데, main에서 3을 넘기고 있어요.
const std = @import("std");
const Foo = enum(u2) {
a,
b,
c,
};
fn foo(a: u2) void {
const b: Foo = @fromBackingInt(a);
std.debug.print("value: {s}\n", .{@tagName(b)});
}
pub fn main() void {
foo(3);
}
foo(3)이 호출되면 유효하지 않은 enum 값을 만드는 순간 런타임에서 막아버려요.
$ zig build-exe runtime_invalid_enum_cast.zig
$ ./runtime_invalid_enum_cast
thread 972816 panic: invalid enum value
/home/ci/work/zig-bootstrap/zig/doc/langref/runtime_invalid_enum_cast.zig:10:20: 0x11e839b in foo (runtime_invalid_enum_cast.zig)
const b: Foo = @fromBackingInt(a);
^
/home/ci/work/zig-bootstrap/zig/doc/langref/runtime_invalid_enum_cast.zig:15:8: 0x11e828e in main (runtime_invalid_enum_cast.zig)
foo(3);
^
/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)
panic: invalid enum value라는 메시지와 함께 프로그램이 종료돼요. @tagName(b)를 호출하기 전에, 바로 @fromBackingInt(a)를 실행하는 시점에서 패닉이 나는 걸 스택 트레이스에서 확인할 수 있어요.
정리하면, 존재하지 않는 태그 값으로 enum을 만드는 건 두 갈래로 막혀요. 컴파일 타임에 값이 확정되면 컴파일 에러, 런타임에만 알 수 있으면 패닉으로요. 어느 쪽이든 유효하지 않은 enum 값은 만들어질 수 없도록 Zig가 스스로 지켜주는 거예요.
더 알아보기
@fromBackingInt는 정수 값을 enum으로 바꾸는 내장 함수예요. 기본 값(@enumFromInt)과 달리 명시적으로 안전 검사를 우회하고 싶을 때 쓰죠.- 유효하지 않은 값을 건네받았을 때 패닉 대신 직접 처리하고 싶다면, 값을 만들기 전에 태그 존재 여부를 먼저 확인하는 방식을 고려해볼 수 있어요.