프로그램 구조

Null·Option·결과·실행 오류

정상적인 값의 부재, null 허용 데이터, 복구 가능한 실패, 런타임 계약 위반을 알맞게 표현하는 방법.

값이 없을 수 있거나 작업이 실패할 수 있을 때 이 페이지를 참고하세요. 먼저 물어야 할 것은 “어느 연산자가 더 짧은가?”가 아니라 “호출자가 계속 진행할 수 있는가?”입니다.

의미에 맞춰 선택하기

상황사용할 형식
토파즈 안에서 값이 없는 것이 정상적인 결과Some(value) 또는 None을 담는 Option<T>
외부 데이터가 명시적으로 null 허용 필드를 가짐T | null
호출자가 오류의 이유를 보고 대응할 수 있음Ok(value) 또는 Err(error)를 담는 Result<T, E>
프로그램이 런타임 계약을 위반함fault. 값을 만들지 않고 실행을 중단함

토파즈 API에서 정상적인 부재는 Option으로 표현하세요. 실제로 null을 포함하는 데이터와 상호 운용 경계에만 null을 남겨 두세요. 실패 이유나 복구 판단이 필요하면 Result를 사용합니다.

잘못된 입력을 복구 가능한 결과로 바꾸기

다음 완전한 프로그램은 toInt가 반환한 값의 부재를 설명이 있는 Result로 바꿉니다.

TOPAZ
record StudyTask {
    title: string,
    minutes: int,
}

function parseMinutes(text: string) -> Result<int, string> {
    match toInt(text) {
        case Some(value) if value > 0 => Ok(value)
        case _ => Err("minutes must be a positive integer")
    }
}

function makeTask(title: string, minuteText: string) -> Result<StudyTask, string> {
    let minutes = parseMinutes(minuteText)?
    Ok(StudyTask { title: title, minutes: minutes })
}

function inspect(minuteText: string) -> () {
    print("input: {minuteText}")
    defer { print("inspection finished") }
    match makeTask("Build application", minuteText) {
        case Ok(task) => print("ready: {task.title}, {task.minutes} minutes")
        case Err(message) => print("cannot add task: {message}")
    }
}

inspect("25")
inspect("later")

검사하고 실행합니다.

BASH
topaz check failures-resources.tpz
topaz run failures-resources.tpz
출력
input: 25
ready: Build application, 25 minutes
inspection finished
input: later
cannot add task: minutes must be a positive integer
inspection finished

정수로 해석할 수 없는 텍스트에는 파싱된 값이 없으므로 toIntOption<int>를 반환합니다. parseMinutes는 이 부재가 호출자에게 중요하다고 판단해 Err로 바꿉니다. makeTask에서 후위 ?Ok를 풀고, Err를 만나면 호환되는 Result 반환 함수에서 그 오류를 즉시 반환합니다. 마지막 match는 두 결과를 명시적으로 처리합니다.

선택 값과 null 허용 값 다루기

선택 접근과 기본값 연산은 두 종류의 부재를 섞지 않습니다.

  • maybeUser: Option<{ name: string }>이면 maybeUser?.name의 결과도 Option이고, maybeUser?.name ?? "guest"string을 만듭니다.
  • configPath: string | null이면 configPath ?? "default.toml"은 값이 null일 때만 기본값을 사용합니다.
  • target ??= value는 대상이 비었을 때만 대입하는 문장입니다. Option<int> 대상에는 42가 아니라 Some(42)가 필요합니다. 토파즈는 Some을 자동으로 씌우지 않습니다.

?.??는 모두 왼쪽을 한 번만 평가합니다. ?.은 수신자가 비어 있으면 메서드 인자도 평가하지 않습니다. 각 연산자는 선택 레이어 하나만 풀거나 평탄화하며, Option을 몰래 null로 바꾸지 않습니다.

흔한 실수 바로잡기: 선택형도 null 허용형도 아닌 값에는 보통의 .을 사용하세요. 타입이 Option<T>null을 함께 포함한다면 단축 연산자를 겹치기보다 각 경우를 match로 명시하세요.

fault는 오류 값이 아닙니다

다음 프로그램은 존재하지 않는 배열 원소를 요구합니다.

TOPAZ
let tasks = ["Run first program", "Build application"]
print(tasks[2])

topaz run intentional-fault.tpz는 0이 아닌 종료 코드로 끝납니다.

출력
error[TPZ4001]: index 2 is out of bounds for an array of length 2
 --> intentional-fault.tpz:2:7
  |
2 | print(tasks[2])
  |       ^^^^^^^^

범위 위반은 fault입니다. 실행을 중단하며 NoneErr도 만들지 않습니다. 후위 ?, ??, 선택 체인, concurrent는 fault를 잡거나 다른 값으로 바꾸지 않습니다. 잘못된 상수 산술처럼 검사 시점에 알 수 있는 형태는 실행 전에 정적 오류로 거부될 수 있습니다.

정확한 경계

  • 후위 ?는 같은 오류 값을 담을 수 있는 반환 타입을 가진 함수나 클로저 안에서만 쓸 수 있습니다.
  • 일반 try, 예외, fault 잡기, panic 키워드, 자동 재시도는 현재 토파즈 형식이 아닙니다.
  • assert(...)는 명시적으로 선택한 test-profile 코드에서만 사용할 수 있으며, 일반 프로그램의 단언문이 아닙니다.
  • 정상적인 스코프 종료의 정리는 defer가 맡습니다. fault 때의 정리 경계는 더 좁으므로 fault를 복구 가능한 Result처럼 다루면 안 됩니다.

안내식 연습은 실패와 리소스, 정리 시점은 Defer와 리소스, 런타임 fault 경계는 실행 환경 동작에서 이어서 확인하세요.