오류에 대한 switch

오류에 대한 switch (Switching on Errors)

함수는 오류를 반환할 수 있고, 우리는 그 오류를 switch로 다룰 때가 많아요. 그런데 Zig에는 오류에 switch를 걸 때만 허용되는 특별한 케이스가 몇 가지 있어요. 이 특수 처리는 제네릭 프로그래밍 패턴을 단순하게 만들어 주는 역할을 해요. 어떤 경우인지 코드로 바로 확인해 볼게요.

출처: Zig Documentation

본문

오류에 switch를 걸 때, 제네릭 프로그래밍 패턴을 단순하게 만들어 주기 위해 몇 가지 특별한 케이스가 허용돼요:

const FileOpenError0 = error{
    AccessDenied,
    OutOfMemory,
    FileNotFound,
};

fn openFile0() FileOpenError0 {
    return error.OutOfMemory;
}

test "unreachable else prong" {
    switch (openFile0()) {
        error.AccessDenied, error.FileNotFound => |e| return e,
        error.OutOfMemory => {},
        // 'openFile0' cannot return any more errors, so an 'else' prong would be
        // statically known to be unreachable. Nonetheless, in this case, adding
        // one does not raise an "unreachable else prong" compile error:
        else => unreachable,
    }

    // Allowed unreachable else prongs are:
    //    `else => unreachable,`
    //    `else => return,`
    //    `else => |e| return e,` (where `e` is any identifier)
}

const FileOpenError1 = error{
    AccessDenied,
    SystemResources,
    FileNotFound,
};

fn openFile1() FileOpenError1 {
    return error.SystemResources;
}

fn openFileGeneric(comptime kind: u1) switch (kind) {
    0 => FileOpenError0,
    1 => FileOpenError1,
} {
    return switch (kind) {
        0 => openFile0(),
        1 => openFile1(),
    };
}

test "comptime unreachable errors not in error set" {
    switch (openFileGeneric(1)) {
        error.AccessDenied, error.FileNotFound => |e| return e,
        error.OutOfMemory => comptime unreachable, // not in `FileOpenError1`!
        error.SystemResources => {},
    }
}
$ zig test test_switch_on_errors.zig
1/2 test_switch_on_errors.test.unreachable else prong...OK
2/2 test_switch_on_errors.test.comptime unreachable errors not in error set...OK
All 2 tests passed.

첫 번째 테스트(unreachable else prong)를 보면, openFile0()이 반환할 수 있는 오류는 AccessDenied, OutOfMemory, FileNotFound 세 가지뿐인데 switch에서 세 오류를 모두 다뤘어요. 그럼 else prong은 실행될 수 없는(unreachable) 게 컴파일 타임에 분명하죠. 그런데도 else => unreachable을 넣었는데 "unreachable else prong" 컴파일 오류가 나지 않아요. 평소라면 오류가 나는 부분인데, 오류에 대한 switch에서는 else => unreachable, else => return, else => |e| return e, 형태의 unreachable else prong만 허용돼요.

두 번째 테스트(comptime unreachable errors not in error set)는 제네릭 함수 openFileGeneric과 시너지를 내요. comptime kind에 따라 반환하는 오류 집합이 달라지고, switch (kind)로 그때그때 맞는 함수를 호출해요. 여기서 openFileGeneric(1)을 호출하면 실제 오류 집합은 FileOpenError1이 돼요. 그런데도 switch에는 FileOpenError1에 없는 error.OutOfMemory prong이 있어요. 이 prong은 comptime unreachable이라 컴파일 타임에 확인되니, "오류 집합에 없는 오류를 다뤘다"는 오류도 나지 않고 실행도 되지 않아요. 두 테스트 모두 통과하는 게 출력에서 확인돼요.

더 알아보기

  • switch의 일반적인 동작과 else prong의 규칙은 Zig 메뉴얼의 switch 섹션에서 다뤄요.
  • 오류 집합이 comptime 값에 따라 바뀌는 제네릭 패턴은 메뉴얼의 comptime 관련 섹션에서 더 자세히 볼 수 있어요.